Compare commits

..

90 Commits

Author SHA1 Message Date
24e1fc18b1 上传真实浏览器仿真推进
结论:新增 LinuxCNC WASM 驱动的真实浏览器仿真页面,接入 browser smoke、release gate、SDK 与项目交付文档,并完成当前推进批次的验收记录。
2026-06-16 20:59:21 +08:00
19eabdf06e 新增 release gate execution summary API 2026-06-16 17:39:47 +08:00
35bc25714d 新增 OPFS session 独立 browser workflow gate 2026-06-16 17:30:23 +08:00
741afc5103 新增 release gate execution manifest API 2026-06-16 17:20:21 +08:00
2f6e8480bf 切换后续记录到 text15 2026-06-16 15:11:36 +08:00
e11c29533f 新增 release artifact URL workflow action plan API 2026-06-16 15:01:39 +08:00
b7ec759645 新增 release artifact URL workflow SDK gate 2026-06-16 14:29:55 +08:00
bee05005b5 新增 release artifact URL workflow summary API 2026-06-16 14:14:14 +08:00
9015de1961 新增 release artifact URL workflow SDK API 2026-06-16 13:54:53 +08:00
d789920d7f 新增 release artifact JSON workflow API 2026-06-16 13:34:48 +08:00
640f1e4438 新增 release artifact validation action plan API 2026-06-16 11:50:38 +08:00
e00c7b038b 新增 release artifact URL workflow summary view model 2026-06-16 11:44:11 +08:00
4970d156c1 补齐 release artifact URL workflow summary 2026-06-16 11:30:55 +08:00
61779cad55 暴露 workflow overview artifact summary 浏览器 API 2026-06-16 11:22:12 +08:00
82bb02de15 新增发布 artifact validation summary view model 2026-06-16 11:08:38 +08:00
ce2c145374 强化发布 readiness artifact gate evidence 校验 2026-06-16 11:01:37 +08:00
9abca15d60 新增项目发布 gate action plan API 2026-06-16 10:51:18 +08:00
1d383b475c 新增项目发布 readiness summary view model 2026-06-16 10:43:18 +08:00
7f937bd56c 新增项目发布 gate result matrix API 2026-06-16 10:37:57 +08:00
343ad0acd6 暴露项目发布 gate manifest API 2026-06-16 10:31:50 +08:00
6c087499fd 修正 text14 续接记录顺序 2026-06-16 10:25:59 +08:00
904f906f86 新增发布 artifact URL browser gate 2026-06-16 10:24:05 +08:00
56175e29cc 推进 workflow overview 发布 artifact URL workflow 2026-06-16 10:15:35 +08:00
b63669588c 推进 workflow overview 发布 artifact DOM workflow 2026-06-16 10:03:23 +08:00
23f337834b 暴露 workflow overview 发布 artifact 校验 API 2026-06-16 09:46:29 +08:00
7177a29fc6 新增发布 readiness artifact SDK 校验 workflow 2026-06-16 09:32:28 +08:00
f4f4b3c744 生成项目发布 readiness gate artifact 2026-06-16 09:17:37 +08:00
ae3adf2d13 新增项目发布 readiness SDK report 2026-06-16 09:09:18 +08:00
d3ee5d49ff 收口 INI 面板 persistence summary 发布门禁 2026-06-16 09:00:29 +08:00
65e95327b3 新增 INI 面板 shell API surface inventory 2026-06-16 07:19:49 +08:00
2615e5bc57 公开 INI 面板 session readiness report SDK surface 2026-06-16 07:08:44 +08:00
ce3d08ef61 新增 INI 面板 session readiness workflow report 2026-06-16 07:04:04 +08:00
eb50e6b455 展示 INI 面板 shell workflow session readiness 2026-06-16 06:52:38 +08:00
68ff2fb5d6 接入 INI 面板只读 session readiness handoff 2026-06-16 06:47:48 +08:00
03db34485e 接入 INI panel session readiness workflow 2026-06-16 06:36:45 +08:00
8e37c69bc4 新增 OPFS session readiness SDK helper 2026-06-16 06:27:31 +08:00
d076763ef4 新增 project release gate wrapper 2026-06-16 04:33:06 +08:00
efbcf8b9bf 完成 current scope clean-tree acceptance 2026-06-16 04:28:29 +08:00
5918a3c573 补充 project release handoff 文档 2026-06-16 04:11:55 +08:00
67a4a69698 补充 host runtime boundary release gate 文档 2026-06-15 22:51:51 +08:00
d16003b67a 补充 sim-config coverage release gate 文档 2026-06-15 22:41:50 +08:00
98ee77f086 补充 OPFS session persistence release gate 文档 2026-06-15 22:33:16 +08:00
f3d8955fd7 推进 SDK API surface exports and smoke 2026-06-15 22:24:17 +08:00
b771ee4141 推进 INI 面板 workflow overview embedding mount DOM wrapper 2026-06-15 22:15:25 +08:00
7a5aa23dbc 推进 INI 面板 workflow overview embedding mount DOM renderer 2026-06-15 22:05:05 +08:00
543609cbe7 推进 INI 面板 workflow overview embedding mount DOM readiness 2026-06-15 21:48:31 +08:00
683f86c33c 新增项目完成情况跟踪文件 2026-06-15 21:33:17 +08:00
861c854309 推进 INI 面板 workflow overview embedding mount DOM contract 2026-06-15 21:23:53 +08:00
ac77de3c13 完善 text14 全项目收口路线图 2026-06-15 21:10:16 +08:00
b51ff3415a 新增 text14 接续执行计划 2026-06-15 21:03:15 +08:00
1a863fa0b4 推进 INI 面板 workflow overview embedding mount render state 2026-06-15 20:57:50 +08:00
fc9e6a3514 推进 INI 面板 workflow overview embedding mount display 2026-06-15 20:46:54 +08:00
83df80d00f 推进 INI 面板 workflow overview embedding mount result 2026-06-15 20:35:39 +08:00
75f71c2563 推进 INI 面板 workflow overview embedding DOM renderer 2026-06-15 20:10:21 +08:00
6856721695 推进 INI 面板 workflow overview embedding DOM readiness 2026-06-15 18:28:58 +08:00
3cfd4b371e 推进 INI 面板 workflow overview embedding DOM contract 2026-06-15 18:20:44 +08:00
f70fc09770 推进 INI 面板 workflow overview embedding render-state 2026-06-15 18:13:30 +08:00
4fdbadd60c 推进 INI 面板 workflow overview embedding display view-model 2026-06-15 18:06:33 +08:00
7095690869 推进 INI 面板 workflow overview embedding report 2026-06-15 17:59:16 +08:00
766f630dfb 推进 INI 面板 workflow overview embedding contract 2026-06-15 17:51:21 +08:00
b61e7e980d 推进 INI 面板 shell handoff workflow overview surface 2026-06-15 15:09:57 +08:00
6b6629ce73 推进 INI 面板 shell handoff workflow DOM mount helper 2026-06-15 14:28:22 +08:00
e1e0c7f6d2 推进 INI 面板 shell handoff workflow render-state helper 2026-06-15 14:11:17 +08:00
1da6180387 推进 INI 面板 shell handoff workflow display-model helper 2026-06-15 14:05:44 +08:00
4c02f33d57 推进 INI 面板 shell handoff workflow summary helper 2026-06-15 13:58:17 +08:00
859d01356b 推进 INI 面板 shell handoff readonly status workflow helper 2026-06-15 13:48:36 +08:00
c3d9b90de1 推进 INI 面板 shell handoff control-page mount workflow helper 2026-06-15 12:23:51 +08:00
298aa5e403 推进 INI 面板 shell handoff mount workflow helper 2026-06-15 12:18:01 +08:00
94b44c0e73 推进 INI 面板 shell handoff mount DOM contract helper 2026-06-15 12:12:01 +08:00
22ca6ced3f 推进 INI 面板 shell handoff mount DOM renderer 2026-06-15 12:02:25 +08:00
54f8551c50 推进 INI 面板 shell integration mount DOM smoke 2026-06-15 11:51:11 +08:00
1375e0ddd5 推进 INI 面板 launch mount-state DOM 渲染 2026-06-15 11:43:00 +08:00
df0773b431 推进 INI 面板 shell handoff mount-state display 2026-06-15 11:31:16 +08:00
f548f24fb8 推进 INI 面板 shell handoff package mount-state 2026-06-15 11:16:35 +08:00
1d06fd4cd4 推进 INI 面板 shell handoff package render-state 2026-06-15 11:07:09 +08:00
bb8ac55905 推进 INI 面板 shell handoff package display view-model 2026-06-15 10:59:02 +08:00
e1029ede0f 推进 INI 面板 shell handoff package report 2026-06-15 10:40:37 +08:00
f1d10fc317 推进 INI 面板 shell handoff package schema 2026-06-15 10:32:45 +08:00
542b139477 推进 INI 面板 shell handoff package 2026-06-15 10:14:25 +08:00
c92021fa82 推进 INI 面板 shell handoff checklist 2026-06-15 10:07:44 +08:00
2a7485c14d 推进 INI 面板 shell handoff action plan 2026-06-15 10:00:40 +08:00
b1f23e6641 推进 INI 面板 shell handoff render-state 2026-06-15 09:51:18 +08:00
5c951ab9bc 推进 INI 面板 shell handoff snapshot API 2026-06-15 09:42:43 +08:00
01817f6493 推进 INI 面板 shell handoff summary browser API 2026-06-15 08:12:33 +08:00
b31b9a2f8a 推进 INI 面板 shell session handoff summary 2026-06-15 08:06:00 +08:00
ee665c47be 推进 INI 面板 shell integration workflow plan 2026-06-15 07:59:51 +08:00
a663c8dbca 推进 INI 面板 shell integration browser workflow smoke 2026-06-15 07:54:19 +08:00
1f9a8c33b0 推进 INI 面板 shell integration readiness helper 2026-06-15 07:48:47 +08:00
7b17603601 推进 INI 面板 shell integration manifest 2026-06-15 07:42:22 +08:00
b3f131fa5f 推进 INI 面板 control-page API manifest predicate 2026-06-15 07:36:48 +08:00
83 changed files with 32718 additions and 16 deletions

View File

@@ -0,0 +1,281 @@
# Project Completion Tracker
Last updated: 2026-06-16 CST
This file tracks the overall completion state of the LinuxCNC WASM/browser port.
Use it for project-level status and acceptance tracking. Use `text15.txt` for
turn-by-turn continuation notes and next-batch execution records.
## Current Status
- Active continuation file: `text15.txt`
- Latest completed batch: real browser simulation page MVP
- Latest relevant commit: use `git log -1 --oneline` after each committed batch
- Working tree at tracker creation: clean
- Required policy: LinuxCNC remains the only CNC semantic source
- First priority: real UI/browser CNC simulation page; see `wasm-port/docs/real-browser-simulation-priority.md`
- Current scope status: complete after final clean-tree release validation
## Completion Definition
The project is complete for the current scope when all of the following are true:
- Browser UI workflows are usable and validated:
- real browser CNC simulation page
- INI panel
- control page
- workflow overview
- external shell handoff
- OPFS/session persistence
- SDK/API surface exposes stable helpers for external callers.
- Sim config coverage has clear pass/skip/promotion state.
- Host/runtime blocked families are explicitly documented and not falsely promoted.
- All required gates pass.
- Documentation explains what is supported, blocked, and how to verify it.
- `text15.txt` contains the current continuation record and the working tree is clean.
## Area Status
| Area | Status | Notes |
| --- | --- | --- |
| LinuxCNC semantic boundary | Stable | JS/browser remains glue, staging, OPFS, WASM/browser boundary, docs, and tests only. |
| Real browser simulation priority | Active first priority | Future batches should prioritize the real UI/browser CNC simulation page, with visible machine/session loading, G-code program state, LinuxCNC-backed interpreter execution, machine readiness/status panels, toolpath/preview rendering from LinuxCNC-produced output or validated runtime events, and browser smoke coverage. First entry point: `wasm-port/runtime/ui/simulation/index.html`; gate: `wasm-port/tests/browser/verify_real_simulation_browser.sh`. See `wasm-port/docs/real-browser-simulation-priority.md`. |
| Real browser simulation page | MVP active | `runtime/ui/simulation/index.html` loads in Chromium, runs a representative G-code program through LinuxCNC interpreter WASM, renders program lines, final axis readout, canonical event table, raw canonical output, and SVG toolpath preview. |
| Vendor/source guard | Stable | `verify_vendor_sync.sh` and standalone semantic guard are required every batch. |
| Interpreter WASM smoke | Stable | `interp_wasm_node_smoke=ok`. |
| Sim config inventory | Stable with skips | `executed=28`, `passed=28`, `skipped=131`, `unexpected_fail=0`; `docs/sim-configs-coverage-handoff.md` records the release-gate baseline. |
| Native nc_files baseline | Stable | Last recorded Layer 1: `total 107`, `pass 101`, `expected_fail 6`, `unexpected_fail 0`. |
| Native sim configs baseline | Stable | Last recorded Layer 2: `total 159`, `pass 151`, `expected_fail 8`, `unexpected_fail 0`. |
| OPFS/session persistence | Stable for current scope | Browser smoke passes, SDK re-exports OPFS/session helpers including `readMachineSessionReadiness()` and `createMachineSessionPersistenceSummary()`, the UI exposes `getMachineSessionPersistenceSummary()`, and `docs/opfs-session-persistence.md` defines the project-level release gate. |
| SDK/API surface | Stable for current scope | SDK re-exports core, OPFS/session readiness/load helpers, sim-config staging, project release gate manifest/result matrix/action-plan/readiness summary view-model, project batch acceptance capability matrix/workflow/summary/action-plan/checklist/report validation summary/action-plan, JSON workflow, URL workflow helpers, and artifact writer/validator gate, project release readiness report/artifact JSON workflow/artifact URL workflow summary/action-plan/artifact validation summary/action-plan plus URL loading workflow, INI panel entry/launcher/control-page/shell/workflow overview helpers including release artifact JSON and URL workflow summary/action-plan/render-state DOM mount helpers, validation action-plan render/mount helpers, URL workflow action-plan, and gate execution summary render/mount helpers, the session readiness workflow report, and the shell API surface inventory with dedicated Node smokes. |
| Browser/UI workflow | Stable for current scope | Workflow overview embedding mount DOM closure is complete, and INI panel session workflows plus read-only control/shell handoff expose, render, mount, fetch, validate, summarize, action-plan, and report OPFS/session readiness, OPFS/session persistence summary, release readiness artifact JSON/URL workflows, validation/rendering/URL workflow summaries/action-plans/gate execution summaries, and API surface inventory before loading or reading WASM session state. |
| Workflow overview embedding | Stable for current scope | Report/display/render, DOM contract/readiness/renderer, mount result/display/render, mount DOM contract/readiness/renderer/wrapper exist. |
| Host/runtime boundary proof | Stable blocked state | Host smoke passes; runtime families remain blocked and `docs/host-runtime-boundary-handoff.md` records host readiness, promotion blockers, dispatch, and evidence gates. |
| Documentation/release gate | Stable for current scope | `docs/project-release-handoff.md` links README, panel entry, SDK, OPFS/session, sim-config, host/runtime, source reuse, drift, tracker acceptance paths, and the machine-readable release readiness report/artifact. |
| Project release gate wrapper | Stable | `tests/host/verify_project_release_gate.sh` runs the documented minimum release validation commands including the release artifact URL browser workflow gate, SDK URL workflow gate, and project batch acceptance workflow/artifact gate, writes `build/project-release-readiness.json` and `build/project-batch-acceptance.json`, verifies embedded gate manifest/result matrix/action-plan evidence plus batch acceptance report evidence through SDK validation workflows, and ends with `project_release_gate=ok`. |
## Blocked Runtime Families
Do not promote these without real LinuxCNC-owned runtime proof and the required
native -> Node/WASM -> browser/host validation sequence:
- `L4-USER-M-PROCESS`
- `L4-TOOL-DB`
- `L4-PYTHON-REMAP`
Do not run these unless the host environment actually provides the needed
LinuxCNC 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
```
## Remaining Work
Execute these in order unless a later user request explicitly changes priority.
### 0. Real Browser CNC Simulation Page
Current first priority:
- Build a real UI/browser CNC simulation page as quickly as possible.
- First MVP exists at `wasm-port/runtime/ui/simulation/index.html`.
- The page must be visible and operator-oriented, not another release dashboard.
- It should use OPFS/session persistence, staged G-code, and existing
LinuxCNC-backed WASM SDK execution.
- It should expose machine/session readiness, active program state, run status,
and a toolpath/preview surface based on LinuxCNC-produced interpreter output,
canonical events, or validated runtime summaries.
- Add browser smoke coverage proving the page loads, renders nonempty runtime
state, and runs a representative LinuxCNC-backed flow.
- Do not implement G-code, tool, parameter, kinematics, remap, planner, or
canonical motion semantics in JavaScript.
Constraint document:
```bash
wasm-port/tests/docs/node/verify_real_browser_simulation_priority_docs.sh
```
### 1. Browser/UI Workflow Closure
Current next task:
- Current scope is complete for the established release baseline. Continue future
work from `text15.txt`, and keep each batch tied to a callable API, verifiable
workflow, executable gate, or real UI/browser capability.
Then complete the closure:
- Mount render-state DOM renderer is complete.
- Non-throwing mount workflow wrapper is complete.
- Expose each helper through:
- `createIniPanelLaunchApiManifest().methods`
- `createIniPanelShellViewModel()`
- `window.linuxCncIniPanelLaunchApi`
- `window.linuxCncIniPanelWorkflowOverviewApi`
- Cover each helper in:
- UI Node smoke
- launch API manifest test
- shell integration manifest test
- entry docs test
- workflow overview browser smoke
- launch browser smoke
- shell integration browser smoke
- `docs/panel-entry.md`
Completion criteria:
- External shell can render workflow overview embedding mount state with a
custom DOM contract.
- Missing DOM returns structured blocked results where applicable.
- No control buttons or CNC semantic parsing are added.
### 2. SDK/API Surface Consolidation
Work items:
- Audit `runtime/sdk/src/index.js`. Done.
- Export stable helpers that external callers should use. Done for core, OPFS/session, sim-config staging, project release readiness, INI panel entry/launcher/control-page/shell/workflow overview helpers.
- Add or update SDK smoke for export shape and manifest compatibility. Done via `tests/sdk/node/verify_sdk_surface.sh`.
- Update `runtime/sdk/README.md` with usage examples and unsupported areas. Done for the current SDK surface.
Completion criteria:
- SDK exports match documented browser/UI helper surface.
- SDK tests pass in Node.
- No CNC semantics are implemented in JS.
### 3. OPFS/Session Persistence Summary
Work items:
- Add a project-level OPFS/session workflow summary helper or report. Done as `docs/opfs-session-persistence.md`.
- Include machine files, session snapshot, readonly control status, and handoff
readiness. Done through linked UI docs plus browser and host gate references.
- Add browser smoke coverage for save -> restore -> readonly status -> shell
handoff. Existing browser smoke remains the gate for this workflow.
- Document OPFS scope and failure modes. Done.
- Add docs smoke for the project-level handoff. Done via `tests/docs/node/verify_opfs_session_docs.sh`.
Completion criteria:
- `browser_ini_opfs_smoke=ok` remains stable.
- External callers can inspect persistence readiness and failures.
- `opfs_session_docs_node_smoke=ok` is included in the host aggregate gate.
### 4. Sim Config Coverage Promotion Matrix
Work items:
- Keep inventory `unexpected_fail=0`.
- Make skip reasons machine-readable and documented:
- `ASSET_ONLY`
- `L4_PYTHON_REMAP`
- `L4_TOOL_DB`
- `L4_USER_M_PROCESS`
- `NON_MAIN_CLASS`
- `UPSTREAM_DEMO`
- Promote only configs with LinuxCNC-owned proof.
- Add short release handoff for current inventory baseline. Done via `docs/sim-configs-coverage-handoff.md`.
- Add docs smoke for inventory baseline and skip reason map. Done via `tests/docs/node/verify_sim_configs_coverage_docs.sh`.
Completion criteria:
- No skipped config lacks a documented reason.
- No blocked runtime family is falsely promoted.
- `sim_configs_coverage_docs_node_smoke=ok` is included in the host aggregate gate.
### 5. Host/Runtime Boundary Reports
Work items:
- Consolidate runtime family proof reports. Done via `docs/host-runtime-boundary-handoff.md`.
- Keep missing host runtime as blocked. Done; current status remains `host_blocked_for_all_opt_in_native_probes`.
- If real runtime becomes available, validate in native -> Node/WASM -> browser/host order.
- Add docs smoke for host/runtime boundary handoff. Done via `tests/docs/node/verify_host_runtime_boundary_docs.sh`.
Completion criteria:
- `verify_host_smokes.sh` remains stable.
- Boundaries are documented and machine-readable.
- `host_runtime_boundary_docs_node_smoke=ok` is included in the host aggregate gate.
### 6. Documentation/Release Gate
Work items:
- Update `README.md`. Done.
- Update `docs/panel-entry.md`. Done.
- Update `docs/source-reuse-map.md`. Done.
- Update `docs/drift-report.md`. Done.
- Add or refresh final acceptance checklist. Done via
`docs/project-release-handoff.md`.
- Add a machine-readable release readiness SDK report for CI/external callers.
Done via `createProjectReleaseReadinessReport()`.
- Add docs smoke for project release handoff. Done via
`tests/docs/node/verify_project_release_handoff_docs.sh`.
Completion criteria:
- A new developer can run validation from README/docs without reading old
`text1` through `text14`.
- `project_release_handoff_docs_node_smoke=ok` is included in the host
aggregate gate.
## Required Gates
Run these for each implementation batch:
```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
SKIP_INI_BUILD=1 SKIP_INTERP_BUILD=1 wasm-port/tests/browser/verify_release_artifact_url_workflow_browser.sh
wasm-port/tests/sdk/node/verify_project_batch_acceptance_workflow.sh
wasm-port/tests/docs/node/verify_real_browser_simulation_priority_docs.sh
SKIP_INTERP_BUILD=1 wasm-port/tests/browser/verify_real_simulation_browser.sh
wasm-port/tests/ui/node/verify_ui_node_smokes.sh
wasm-port/tests/host/verify_host_smokes.sh
```
Or run the same current-scope minimum gate through:
```bash
wasm-port/tests/host/verify_project_release_gate.sh
```
Run this if browser interpreter behavior is touched:
```bash
SKIP_INI_BUILD=1 SKIP_INTERP_BUILD=1 wasm-port/tests/browser/verify_interp_browser.sh
```
Run these for native/source/staging or promotion work:
```bash
wasm-port/tests/native/verify_nc_files.sh
wasm-port/tests/native/verify_sim_configs.sh
```
## Final Acceptance Checklist
- [x] Working tree is clean.
- [x] Latest continuation state has moved to `text15.txt`.
- [x] `git diff --check` passes.
- [x] Vendor sync guard passes.
- [x] Standalone CNC semantics guard passes.
- [x] Interpreter WASM Node smoke passes.
- [x] Sim config WASM inventory has `unexpected_fail=0`.
- [x] UI Node smoke passes.
- [x] Browser INI panel smoke passes.
- [x] Host aggregate smoke passes.
- [x] Project release gate wrapper passes.
- [x] Runtime blocked families remain documented.
- [x] Project release handoff docs smoke passes.
- [x] README/docs explain supported workflows and blocked items.

1947
text12.txt

File diff suppressed because it is too large Load Diff

2040
text13.txt Normal file

File diff suppressed because it is too large Load Diff

4224
text14.txt Normal file

File diff suppressed because it is too large Load Diff

1632
text15.txt Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -2,6 +2,59 @@
This directory is the standalone workspace for the WASM-based simulation port.
## Current Release Handoff
Start from `docs/project-release-handoff.md` for the current project-level
handoff. It links the browser/UI workflow surface, SDK surface,
OPFS/session persistence scope, sim-config coverage baseline, blocked
host/runtime families, source reuse rules, drift rules, the real browser
simulation priority constraint, and acceptance tracker.
The first priority for subsequent work is documented in
`docs/real-browser-simulation-priority.md`: move quickly toward a real browser
CNC simulation page with visible machine/session loading, G-code program state,
LinuxCNC-backed interpreter execution, and browser smoke coverage. This does
not relax the LinuxCNC semantic boundary; browser code still must not implement
G-code, tool, parameter, kinematics, remap, or planner semantics.
The first real simulation entry point is `runtime/ui/simulation/index.html`,
validated by `tests/browser/verify_real_simulation_browser.sh`.
The project release gate for the current scope is:
```bash
wasm-port/tests/host/verify_project_release_gate.sh
```
It executes these minimum release validation commands:
```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
```
Expected handoff smoke outputs include:
```text
project_release_handoff_docs_node_smoke=ok
opfs_session_docs_node_smoke=ok
sim_configs_coverage_docs_node_smoke=ok
host_runtime_boundary_docs_node_smoke=ok
real_browser_simulation_priority_docs_node_smoke=ok
sdk_surface_node_smoke=ok
host_wasm_opfs_browser_smokes=ok
project_release_gate=ok
```
Browser/UI entry points are documented in `docs/panel-entry.md`. Stable SDK
imports are documented in `runtime/sdk/README.md` and should come from
`runtime/sdk/src/index.js`.
Rules:
1. `linuxcnc/` is treated as the upstream source tree.
@@ -130,5 +183,16 @@ Directory intent:
- `tests/fixtures/`: stable simulation inputs shared by native and browser tests.
- `build/` and `dist/`: generated outputs only; never hand-edited.
Project-level OPFS/session persistence and release-gate handoff is documented in
`docs/opfs-session-persistence.md`.
The real browser CNC simulation page priority is documented in
`docs/real-browser-simulation-priority.md`.
Sim-config inventory coverage and release-gate handoff is documented in
`docs/sim-configs-coverage-handoff.md`.
Host/runtime boundary release-gate handoff is documented in
`docs/host-runtime-boundary-handoff.md`.
Project-level release acceptance and next-maintainer handoff is documented in
`docs/project-release-handoff.md`.
The presence of this directory means the port is managed independently of the
native LinuxCNC tree even though LinuxCNC remains the semantic source of truth.

View File

@@ -6,6 +6,10 @@ As of the current upstream baseline, no byte-level drift is allowed between
files listed in `tools/source-manifest.txt` and the matching files under
`../linuxcnc/`.
The project-level release handoff in `docs/project-release-handoff.md` includes
this drift report in the final acceptance path alongside source reuse,
OPFS/session, sim-config coverage, host/runtime boundary, SDK, and UI docs.
The enforced upstream baseline is:
```text

View File

@@ -0,0 +1,146 @@
# Host/runtime boundary release handoff
This document is the short release-gate handoff for blocked LinuxCNC runtime
families in the WASM/browser port. It summarizes current host readiness,
promotion blockers, opt-in probe dispatch state, native evidence acceptance, and
the rule that blocked runtime families remain blocked on this host.
Detailed references:
- `docs/full-process-boundary-design.md`
- `docs/sim-configs-coverage-matrix.md`
- `docs/sim-configs-coverage-handoff.md`
- `tests/wasm/node/verify_sim_configs_inventory_wasm.mjs`
## Current blocked runtime families
The current hard blocked families are:
- `L4-USER-M-PROCESS`
- `L4-TOOL-DB`
- `L4-PYTHON-REMAP`
Current host readiness is summarized by:
- `build/wasm/sim-configs-inventory/runtime-boundary-host-readiness-rollup.tsv`
- `build/wasm/sim-configs-inventory/runtime-boundary-promotion-blockers.tsv`
- `build/wasm/sim-configs-inventory/runtime-boundary-opt-in-probe-dispatch-rollup.tsv`
- `build/wasm/sim-configs-inventory/runtime-boundary-native-evidence-acceptance-gate.tsv`
- `build/wasm/sim-configs-inventory/runtime-boundary-opt-in-probe-skip-evidence-rollup.tsv`
- `build/wasm/sim-configs-inventory/blocked-runtime-promotion-lock.tsv`
Current rollup state:
```text
host_readiness_status=host_blocked_for_all_opt_in_native_probes
family_count=3
host_ready_family_count=0
host_blocked_family_count=3
blocked_families=L4-PYTHON-REMAP,L4-TOOL-DB,L4-USER-M-PROCESS
missing_host_requirements=halcmd,halrun,linuxcnc,milltask
ready_opt_in_command_count=0
execution_enabled=0
promotion_allowed=0
```
## Promotion blockers
Each blocked family currently has `promotion_ready=0`, `execution_enabled=0`,
and `promotion_allowed=0`.
The current blocker keys are:
```text
host_runtime_requirements_missing
native_runtime_probe_not_passed
native_pass_evidence_not_ready
node_inventory_gate_not_complete
browser_smoke_gate_not_complete
promotion_lock_active
manual_lock_update_required
```
The next unblock action is to provide the missing host runtime requirements for
the relevant family. Providing host commands is not enough to promote a row; it
only allows the next opt-in probe gate to be considered.
## Opt-in probe dispatch
The current dispatch rollup is:
```text
dispatch_status=dispatch_blocked_for_all_opt_in_native_probes
probe_count=3
dispatch_allowed_count=0
dispatch_blocked_count=3
dispatch_actions=skip_missing_host_requirements
execution_enabled=0
promotion_allowed=0
```
These commands remain blocked on this host:
```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
```
Do not run them unless the generated preflight, family readiness, dispatch
plan, and dispatch rollup all permit the specific family.
## Native evidence acceptance
`runtime-boundary-native-evidence-acceptance-gate.tsv` currently records:
- `current_probe_status=skipped_missing_host_runtime`
- `native_evidence_status=pending_until_native_pass`
- `skip_evidence_status=skip_valid_until_host_requirements_available`
- `node_gate_status=blocked_until_native_pass_evidence`
- `browser_gate_status=blocked_until_node_gate_complete`
- `native_evidence_gate=blocked_until_host_requirements_available`
- `evidence_acceptance_allowed=0`
A skipped opt-in probe is not a native pass. It only proves that this host is
correctly blocked until the required LinuxCNC runtime commands are available.
## Release gate
Use this gate when touching blocked runtime family reports, host readiness, or
promotion policy:
```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_sim_configs_inventory_wasm.sh
wasm-port/tests/docs/node/verify_host_runtime_boundary_docs.sh
wasm-port/tests/docs/node/verify_sim_configs_coverage_docs.sh
wasm-port/tests/host/verify_host_smokes.sh
```
The host aggregate must include:
```text
sim_configs_wasm_node_inventory=ok
host_runtime_boundary_docs_node_smoke=ok
sim_configs_coverage_docs_node_smoke=ok
host_wasm_opfs_browser_smokes=ok
```
## Promotion rule
No blocked family may be promoted until all of these are true:
1. Host readiness allows the family-specific opt-in probe.
2. The explicit opt-in probe passes natively with LinuxCNC-owned runtime
behavior.
3. Native evidence acceptance allows the pass evidence.
4. Node/WASM inventory proof passes after native evidence.
5. Browser/host proof passes after Node/WASM proof.
6. `verify_no_standalone_cnc_semantics.sh` still passes.
7. `blocked-runtime-promotion-lock.tsv`, docs, and tracker records are updated
deliberately.
Until then, `L4-USER-M-PROCESS`, `L4-TOOL-DB`, and `L4-PYTHON-REMAP` remain
blocked, inventory-only runtime families.

View File

@@ -0,0 +1,180 @@
# OPFS/session persistence and release gate
This document is the project-level handoff for OPFS/session persistence in the
LinuxCNC WASM/browser port. It links the stable SDK entrypoint, browser smoke,
session snapshot helpers, machine-file helpers, host gate commands, and blocked
runtime family policy in one place.
## Supported surface
Stable external imports come from `runtime/sdk/src/index.js`.
OPFS text/file helpers:
- `getOpfsRoot()`
- `saveTextFile()`
- `loadTextFile()`
- `machineIniPath()`
- `toolTablePath()`
- `parameterFilePath()`
- `gcodeProgramPath()`
- `sessionSnapshotPath()`
- `defaultMachinePaths()`
- `normalizeOpfsPath()`
Session snapshot helpers:
- `createMachineSessionSnapshotPayload()`
- `createSessionSnapshot()`
- `saveSessionSnapshot()`
- `saveMachineSessionSnapshot()`
- `loadSessionSnapshot()`
- `loadMachineSessionSnapshot()`
- `validateSessionSnapshot()`
- `readMachineSessionReadiness()`
Machine file and program helpers:
- `machineFilePaths()`
- `saveMachineTextFiles()`
- `loadMachineTextFiles()`
- `saveGcodeProgram()`
- `loadGcodeProgram()`
- `gcodeFilenameFromProgramPath()`
LinuxCNC-backed OPFS bridges:
- `restoreMachineParametersFromOpfs()`
- `saveMachineParametersToOpfs()`
- `loadMachineToolTableFromOpfs()`
- `saveMachineToolTableToOpfs()`
- `loadMachineSessionFromOpfs()`
Browser UI/session helpers are documented in:
- `docs/ui-session-summary-helpers.md`
- `docs/ui-machine-file-helpers.md`
- `docs/ui-run-summary-helpers.md`
- `docs/ui-panel-state-summary.md`
- `docs/panel-entry.md`
## Persistence workflow
The browser/UI persistence workflow is host-side only:
1. The UI saves machine INI, parameter, and tool-table text under
`linuxcnc/machines/<machine-id>/`.
2. Optional G-code programs are saved under `linuxcnc/gcode/`.
3. A session snapshot is saved under `linuxcnc/sessions/<session-id>/`.
4. `loadMachineSessionFromOpfs()` loads the persisted INI text, copies it into
the interpreter WASM filesystem, and uses the LinuxCNC-backed INI SDK to read
`[RS274NGC]PARAMETER_FILE`, `[EMCIO]TOOL_TABLE`, and
`[EMCIO]RANDOM_TOOLCHANGER` when explicit host options do not override them.
5. Parameter and tool-table bridge helpers copy OPFS text into the interpreter
WASM filesystem and then call LinuxCNC-backed interpreter SDK methods such as
`restoreParameters()`, `saveParameters()`, `loadToolTable()`, and
`saveToolTable()`.
External callers can run `readMachineSessionReadiness()` before step 4 to get a
structured `ready`/`blocked` report for persisted INI, parameter, tool-table,
optional G-code, and optional session snapshot files. This helper only checks
host file availability and snapshot envelope validity.
This workflow stores host files and stages them into Emscripten FS. It does not
implement G-code, tool-table, parameter-file, planner, remap, or kinematics
semantics in JavaScript.
## Verified browser workflows
The aggregate browser gate is:
```bash
SKIP_INI_BUILD=1 SKIP_INTERP_BUILD=1 wasm-port/tests/browser/verify_ini_panel_browser.sh
```
The dedicated OPFS/session browser workflow gate is:
```bash
SKIP_INI_BUILD=1 SKIP_INTERP_BUILD=1 wasm-port/tests/browser/verify_opfs_session_workflow_browser.sh
```
Required output:
```text
browser_ini_opfs_smoke=ok
browser_opfs_session_workflow_smoke=ok
browser_ini_control_page_smoke=ok
browser_ini_launch_smoke=ok
browser_ini_workflow_overview_smoke=ok
browser_ini_shell_integration_workflow_smoke=ok
```
`browser_ini_opfs_smoke=ok` covers real browser OPFS/session persistence,
including INI text persistence, session snapshots, machine-file helpers,
G-code program helpers, and the INI panel frame workflow.
`browser_opfs_session_workflow_smoke=ok` isolates the save machine files ->
save G-code -> save session snapshot -> readiness -> load session into WASM ->
persistence summary workflow in a standalone browser gate.
## Release gate
Run the project-level gate before claiming a release-ready OPFS/session or SDK
surface:
```bash
git diff --check
wasm-port/tools/verify_vendor_sync.sh
wasm-port/tools/verify_no_standalone_cnc_semantics.sh
wasm-port/tests/sdk/node/verify_sdk_surface.sh
wasm-port/tests/opfs/node/verify_file_service.sh
wasm-port/tests/ui/node/verify_ui_node_smokes.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
SKIP_INI_BUILD=1 SKIP_INTERP_BUILD=1 wasm-port/tests/browser/verify_opfs_session_workflow_browser.sh
wasm-port/tests/host/verify_host_smokes.sh
```
The host aggregate must include:
```text
opfs_file_service_node_smoke=ok
sdk_surface_node_smoke=ok
ui_node_smokes=ok
browser_ini_opfs_smoke=ok
browser_opfs_session_workflow_smoke=ok
browser_ini_shell_integration_workflow_smoke=ok
host_wasm_opfs_browser_smokes=ok
```
## Blocked runtime families
OPFS/session persistence does not unblock full LinuxCNC runtime families. These
remain blocked until a LinuxCNC-owned runtime proof exists and native,
Node/WASM, browser, and host gates all pass in that order:
- `L4-USER-M-PROCESS`
- `L4-TOOL-DB`
- `L4-PYTHON-REMAP`
Do not run the opt-in runtime probes on a host that lacks the required LinuxCNC
runtime processes:
```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
```
## Failure modes
- Missing OPFS support returns an OPFS availability error from `getOpfsRoot()`.
- Invalid OPFS paths are rejected before storage access.
- Invalid snapshot format, version, session id, payload, or machine id is
rejected by snapshot validation helpers.
- `readMachineSessionReadiness()` reports missing files or invalid snapshots as
structured blocked checks before callers load a session into WASM.
- Missing INI, parameter, or tool-table OPFS files remain host persistence
errors. JavaScript does not synthesize LinuxCNC machine state.
- Tool, parameter, remap, planner, and kinematics behavior remains owned by
vendored LinuxCNC source and the C/WASM ABI.

View File

@@ -2,6 +2,12 @@
This page is the short entry map for the INI panel UI shell.
For the project-level acceptance path that references this UI surface, start
with `docs/project-release-handoff.md`. Stable SDK imports for external callers
are documented in `runtime/sdk/README.md` and should come from
`runtime/sdk/src/index.js`. The workflow overview embedding helpers below are
the read-only external-shell path for embedding and mounting overview state.
## Shell Module
Use the shell-facing module when an outer UI needs entry links or a read-only
@@ -10,24 +16,94 @@ control view:
```js
import {
INI_PANEL_ENTRIES,
createControlPageBrowserApiManifest,
createControlPageBrowserApiMethods,
createControlPageBrowserApiSchema,
createIniPanelLaunchApiManifest,
createIniPanelLaunchApiSchema,
createIniPanelLauncherLinkViewModel,
createIniPanelShellControlPageContract,
createIniPanelShellIntegrationManifest,
createIniPanelShellIntegrationReadiness,
createIniPanelShellIntegrationSchema,
createIniPanelShellIntegrationWorkflowPlan,
createIniPanelShellSessionHandoffActionPlan,
createIniPanelShellSessionHandoffChecklist,
createIniPanelShellSessionHandoffCompatibilityReport,
createIniPanelShellSessionHandoffDisplayViewModel,
createIniPanelShellSessionHandoffPackage,
createIniPanelShellSessionHandoffPackageDisplayViewModel,
createIniPanelShellSessionHandoffPackageMountDisplayViewModel,
createIniPanelShellSessionHandoffPackageMountRenderState,
createIniPanelShellSessionHandoffPackageMountState,
createIniPanelShellSessionHandoffMountDomContract,
createIniPanelShellSessionHandoffMountDomReadiness,
mountIniPanelShellSessionHandoff,
mountIniPanelShellSessionHandoffFromControlPage,
createIniPanelShellSessionHandoffWorkflowSummary,
createIniPanelShellSessionReadinessWorkflowReport,
createIniPanelShellApiSurfaceInventory,
createIniPanelShellSessionHandoffWorkflowDisplayViewModel,
createIniPanelShellSessionHandoffWorkflowRenderState,
createIniPanelShellSessionHandoffWorkflowDomContract,
createIniPanelShellSessionHandoffWorkflowDomReadiness,
createIniPanelShellWorkflowOverviewContract,
createIniPanelShellWorkflowOverviewReadiness,
createIniPanelShellWorkflowOverviewEmbeddingReport,
createIniPanelShellWorkflowOverviewEmbeddingDisplayViewModel,
createIniPanelShellWorkflowOverviewEmbeddingRenderState,
createIniPanelShellWorkflowOverviewEmbeddingDomContract,
createIniPanelShellWorkflowOverviewEmbeddingDomReadiness,
mountIniPanelShellWorkflowOverviewEmbedding,
renderIniPanelShellWorkflowOverviewEmbeddingState,
mountIniPanelShellWorkflowOverviewEmbeddingState,
createIniPanelShellWorkflowOverviewEmbeddingMountDisplayViewModel,
createIniPanelShellWorkflowOverviewEmbeddingMountRenderState,
createIniPanelShellWorkflowOverviewEmbeddingMountDomContract,
createIniPanelShellWorkflowOverviewEmbeddingMountDomReadiness,
renderIniPanelShellWorkflowOverviewEmbeddingMountState,
mountIniPanelShellWorkflowOverviewEmbeddingMountState,
validateIniPanelShellWorkflowOverviewReleaseReadinessArtifactJson,
loadIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrl,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactDisplayViewModel,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactRenderState,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactDomContract,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactDomReadiness,
renderIniPanelShellWorkflowOverviewReleaseReadinessArtifactState,
mountIniPanelShellWorkflowOverviewReleaseReadinessArtifactState,
renderIniPanelShellSessionHandoffWorkflowState,
mountIniPanelShellSessionHandoffWorkflow,
renderIniPanelShellSessionHandoffMountState,
createIniPanelShellSessionHandoffPackageReport,
createIniPanelShellSessionHandoffPackageRenderState,
createIniPanelShellSessionHandoffPackageSchema,
createIniPanelShellSessionHandoffRenderState,
createIniPanelShellSessionHandoffSnapshot,
createIniPanelShellSessionHandoffSummary,
createIniPanelShellSessionHandoffSummarySchema,
createIniPanelShellViewModel,
getIniPanelEntryByHref,
getVisibleIniPanelEntries,
createMachineSessionPersistenceDisplayViewModel,
createMachineSessionPersistenceRenderState,
createMachineSessionPersistenceSummary,
createMachineSessionControlPageController,
createMachineSessionReadonlyStatus,
createMachineSessionReadonlyStatusApiManifest,
createMachineSessionReadinessFieldState,
machineSessionReadinessLogLines,
readMachineSessionReadinessWorkflow,
readMachineSessionReadonlyStatus,
loadMachineSessionForControlPageWorkflow,
createControlPageSessionLoadState,
refreshMachineSessionControlPage,
controlPageStatusText,
renderMachineSessionControlView,
isSupportedIniPanelControlPageApiManifest,
isSupportedIniPanelLaunchApiManifest,
isSupportedIniPanelShellIntegrationManifest,
isSupportedIniPanelShellSessionHandoffPackage,
isSupportedIniPanelShellSessionHandoffSummary,
} from "../runtime/ui/ini-panel/ui-shell.js";
```
@@ -42,6 +118,7 @@ and the control-view renderer. It does not implement CNC behavior.
| Launch | `runtime/ui/ini-panel/launch.html` | Read-only entry page for available panel surfaces. |
| 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. |
| Workflow overview | `runtime/ui/ini-panel/workflow-overview.html` | Read-only shell handoff workflow overview surface. |
## Visible Entries
@@ -51,6 +128,7 @@ The visible launch entries come from `getVisibleIniPanelEntries()`:
| --- | --- | --- |
| `edit-run` | `./index.html` | Open edit and run panel |
| `control-view` | `./control-page.html` | Open read-only control view |
| `workflow-overview` | `./workflow-overview.html` | Open shell handoff overview |
Launcher pages can render links from `createIniPanelLauncherLinkViewModel()`
without reading manifest field names directly. Each item has `entryId`, `href`,
@@ -61,10 +139,101 @@ read-only model with `pages`, `launcherLinks`, and `controlPage` helpers for
mounting the existing control-page controller, refresh workflow, and renderer.
Use `controlPage.readStatus` or `readMachineSessionReadonlyStatus()` when an
outer shell needs one stable read of `stateBundle`, `workflowStatus`,
`overview`, `controlView`, `session`, `runReadiness`, and `run`.
`overview`, `controlView`, `sessionReadiness`, `session`, `runReadiness`, and
`run`.
Use `createMachineSessionReadonlyStatusApiManifest()` or
`controlPage.readonlyStatusApiManifest` when you only need the API shape and
method names.
Use `readMachineSessionReadinessWorkflow()` when an outer shell or panel flow
needs a read-only `ready`/`blocked` readiness report before loading a machine
session into WASM. `createMachineSessionReadinessFieldState()` and
`machineSessionReadinessLogLines()` turn that report into stable fields and
status text without parsing G-code or executing LinuxCNC behavior.
Use `createMachineSessionPersistenceSummary()` or
`linuxCncIniPanelApi.getMachineSessionPersistenceSummary()` when a browser
panel or outer shell needs one read-only OPFS/session persistence summary with
machine-file, session snapshot, session readiness, session load, readonly
status, and handoff readiness rows. The launch page exposes the same helper as
`linuxCncIniPanelLaunchApi.getMachineSessionPersistenceSummary()`, and the
workflow overview page exposes it as
`linuxCncIniPanelWorkflowOverviewApi.getWorkflowOverviewPersistenceSummary()`.
Use `validateIniPanelShellWorkflowOverviewReleaseReadinessArtifactJson()` or
`linuxCncIniPanelWorkflowOverviewApi.validateWorkflowOverviewReleaseReadinessArtifactJson()`
when an outer shell needs to validate a project release readiness artifact JSON
through the same read-only workflow overview surface. It parses the artifact,
returns stable validation rows, and does not execute release gates or CNC
behavior.
Use `runIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflow()` or
`linuxCncIniPanelWorkflowOverviewApi.runWorkflowOverviewReleaseReadinessArtifactJsonWorkflow()`
when the shell already has artifact JSON and needs parse, validation, summary,
action-plan, and gate execution summary output in one workflow result.
Use `createIniPanelShellWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryViewModel()` or
`linuxCncIniPanelWorkflowOverviewApi.getWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryViewModel()`
when the shell needs the artifact's release gate execution summary rows.
Use `createIniPanelShellWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryRenderState()`,
`createIniPanelShellWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryDomContract()`,
`createIniPanelShellWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryDomReadiness()`,
`renderIniPanelShellWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryState()`, or
`mountIniPanelShellWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryState()` when the
shell needs to render those per-gate rows into caller-owned DOM.
Use `createIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflowSummaryViewModel()` or
`linuxCncIniPanelWorkflowOverviewApi.getWorkflowOverviewReleaseReadinessArtifactJsonWorkflowSummaryViewModel()`
when the shell needs stable status text and rows for that JSON workflow.
Use `createIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflowActionPlan()` or
`linuxCncIniPanelWorkflowOverviewApi.getWorkflowOverviewReleaseReadinessArtifactJsonWorkflowActionPlan()`
when the shell needs the JSON workflow next-command plan without inspecting
internal workflow fields.
Use `createIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflowDomContract()`,
`createIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflowDomReadiness()`,
`renderIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflowState()`, or
`mountIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflowState()` when the
shell needs to render that JSON workflow state into caller-owned DOM.
Use `loadIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrl()` or
`linuxCncIniPanelWorkflowOverviewApi.loadWorkflowOverviewReleaseReadinessArtifactUrl()`
when the shell should fetch the artifact JSON URL, validate it, and optionally
mount the render-state into caller-owned DOM without executing gates. The URL
workflow result includes `validationSummaryViewModel` so callers can show
stable validation status and rows from the same fetch.
Use `createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowSummaryViewModel()` or
`linuxCncIniPanelWorkflowOverviewApi.getWorkflowOverviewReleaseReadinessArtifactUrlWorkflowSummaryViewModel()`
when the shell needs one stable status line and dashboard rows for the whole
artifact URL fetch/validate/render/mount workflow.
Use `createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowRenderState()`,
`createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowDomContract()`,
`createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowDomReadiness()`,
`renderIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowState()`, or
`mountIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowState()` when the
shell needs to render that URL workflow state into caller-owned DOM.
Use `createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowActionPlan()` or
`linuxCncIniPanelWorkflowOverviewApi.getWorkflowOverviewReleaseReadinessArtifactUrlWorkflowActionPlan()`
when the shell needs the ordered next-action/next-command plan for that URL
workflow without executing release gates.
Use `createIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationSummaryViewModel()` or
`linuxCncIniPanelWorkflowOverviewApi.getWorkflowOverviewReleaseReadinessArtifactValidationSummaryViewModel()`
when the shell needs stable artifact validation status text and dashboard rows
without interpreting validation flags itself.
Use `createIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationActionPlan()` or
`linuxCncIniPanelWorkflowOverviewApi.getWorkflowOverviewReleaseReadinessArtifactValidationActionPlan()`
when direct artifact JSON validation needs its own ordered next-command plan
without executing release gates.
Use `createIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationActionPlanRenderState()`,
`createIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationActionPlanDomContract()`,
`createIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationActionPlanDomReadiness()`,
`renderIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationActionPlanState()`, or
`mountIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationActionPlanState()` when the
shell needs to render that validation action plan into caller-owned DOM.
Use `createIniPanelShellWorkflowOverviewReleaseReadinessArtifactDisplayViewModel()`,
`createIniPanelShellWorkflowOverviewReleaseReadinessArtifactRenderState()`,
`createIniPanelShellWorkflowOverviewReleaseReadinessArtifactDomContract()`,
`createIniPanelShellWorkflowOverviewReleaseReadinessArtifactDomReadiness()`,
`renderIniPanelShellWorkflowOverviewReleaseReadinessArtifactState()`, or
`mountIniPanelShellWorkflowOverviewReleaseReadinessArtifactState()` when that
validation needs to be rendered into caller-owned DOM. The workflow overview
page exposes equivalent methods with the `getWorkflowOverview...`,
`renderWorkflowOverview...`, and `mountWorkflowOverview...` prefixes.
Use `createMachineSessionPersistenceDisplayViewModel()` and
`createMachineSessionPersistenceRenderState()` when that summary needs stable
display text, rows, status line, and dataset fields for caller-owned DOM.
Use `createIniPanelShellControlPageContract()` or `controlPage.contract` when
an outer shell needs a fast contract check for `createController`,
`loadSessionState`, `readStatus`, `refresh`, `renderView`, the readonly status
@@ -78,6 +247,14 @@ smokes can verify the same API shape before reading the embedded panel state.
Use `linuxCncIniPanelControlPageApi.getControlPageApiMethods()` when a browser
outer page needs the exact exposed method list before calling the read-only
control page API.
Use `createControlPageBrowserApiSchema()`,
`createControlPageBrowserApiManifest()`, or
`linuxCncIniPanelControlPageApi.getControlPageApiSchema()` and
`linuxCncIniPanelControlPageApi.getControlPageApiManifest()` when a browser
outer page needs a versioned control-page API description before reading the
embedded panel. Use `isSupportedIniPanelControlPageApiManifest()` or
`linuxCncIniPanelControlPageApi.isSupportedControlPageApiManifest()` for a
read-only compatibility predicate before consuming that manifest.
Use `linuxCncIniPanelControlPageApi.readControlPageStatus()` when a browser
outer page needs the same readonly status snapshot from the real embedded
control page after save/load session workflows.
@@ -96,6 +273,273 @@ fields.
Use `isSupportedIniPanelLaunchApiManifest()` or
`linuxCncIniPanelLaunchApi.isSupportedLaunchApiManifest()` for a read-only
compatibility predicate before using a launch manifest.
Use `createIniPanelShellIntegrationSchema()`,
`createIniPanelShellIntegrationManifest()`,
`linuxCncIniPanelLaunchApi.getShellIntegrationSchema()`, or
`linuxCncIniPanelLaunchApi.getShellIntegrationManifest()` when an outer shell
needs one versioned read of pages, launcher links, launch API metadata,
control-page API metadata, OPFS/session persistence capabilities, and current
compatibility status. Use `isSupportedIniPanelShellIntegrationManifest()` when
the outer shell needs to check that integration manifest before consuming it.
Use `createIniPanelShellIntegrationReadiness()` or
`linuxCncIniPanelLaunchApi.getShellIntegrationReadiness()` when the outer shell
needs a read-only `ready`/`blocked` status, failed check names, API names, and
counts for pages, methods, and persistence capabilities before mounting any
embedded control page.
Use `createIniPanelShellIntegrationWorkflowPlan()` when the outer shell needs a
Node-testable read-only plan for the control-page target, required launch and
control-page API methods, readiness gate, and readonly status handoff fields
before opening a browser frame.
Use `createIniPanelShellSessionHandoffSummary()` when the outer shell needs one
read-only display summary of readiness, target page, persistence capabilities,
readonly status fields, handoff methods, and blocking reasons. Browser shells
can read the same summary through
`linuxCncIniPanelLaunchApi.getShellSessionHandoffSummary()`.
Use `createIniPanelShellSessionHandoffSummarySchema()` or
`linuxCncIniPanelLaunchApi.getShellSessionHandoffSummarySchema()` for the
versioned summary shape, and `isSupportedIniPanelShellSessionHandoffSummary()`
or `linuxCncIniPanelLaunchApi.isSupportedShellSessionHandoffSummary()` before
consuming a summary from a browser shell.
Use `createIniPanelShellSessionHandoffCompatibilityReport()` or
`linuxCncIniPanelLaunchApi.getShellSessionHandoffCompatibilityReport()` when an
outer shell needs display-ready diagnostics for schema support, missing fields,
api/version mismatches, and current blocking reasons before consuming the
handoff summary.
Use `createIniPanelShellSessionHandoffDisplayViewModel()` or
`linuxCncIniPanelLaunchApi.getShellSessionHandoffDisplayViewModel()` when an
outer shell needs the same diagnostics as stable read-only label/value rows for
DOM display.
Use `createIniPanelShellSessionHandoffSnapshot()` or
`linuxCncIniPanelLaunchApi.getShellSessionHandoffSnapshot()` when an outer shell
needs one read-only API read that includes readiness, handoff summary,
compatibility report, and display view-model together.
Use `createIniPanelShellSessionHandoffRenderState()` or
`linuxCncIniPanelLaunchApi.getShellSessionHandoffRenderState()` when an outer
shell needs the snapshot compressed into launch-side DOM state with a status
line, stable rows, and `[data-handoff-shell]` phase/readiness flags. The launch
page renders `[data-handoff-status]` and `[data-handoff-rows]` from this helper
without adding control actions.
Use `createIniPanelShellSessionHandoffActionPlan()` or
`linuxCncIniPanelLaunchApi.getShellSessionHandoffActionPlan()` when an outer
shell needs a read-only next-step description derived from the same snapshot:
target page, required readonly methods, blocking reasons, display rows, and
ready/blocked next step ids. The action plan describes what an outer shell can
do next; it does not execute actions or add controls.
Use `createIniPanelShellSessionHandoffChecklist()` or
`linuxCncIniPanelLaunchApi.getShellSessionHandoffChecklist()` when an outer
shell needs the same handoff state as stable health rows for display: render
state, target page, load-session method, read-status method, blocking reasons,
display rows, and next step ids. The checklist is read-only and does not
execute the action plan.
Use `createIniPanelShellSessionHandoffPackage()` or
`linuxCncIniPanelLaunchApi.getShellSessionHandoffPackage()` when an outer shell
needs one browser API read containing the launch/control method manifests,
snapshot, render-state, action-plan, and checklist. The package is a read-only
handoff artifact and does not execute action-plan steps.
Use `createIniPanelShellSessionHandoffPackageSchema()` or
`linuxCncIniPanelLaunchApi.getShellSessionHandoffPackageSchema()` for the
versioned package shape, and `isSupportedIniPanelShellSessionHandoffPackage()`
or `linuxCncIniPanelLaunchApi.isSupportedShellSessionHandoffPackage()` before
consuming a package from a browser shell.
Use `createIniPanelShellSessionHandoffPackageReport()` or
`linuxCncIniPanelLaunchApi.getShellSessionHandoffPackageReport()` when an outer
shell needs display-ready diagnostics for package schema support, missing
package fields, missing manifest fields, api/version mismatches, and checklist
blocking reasons.
Use `createIniPanelShellSessionHandoffPackageDisplayViewModel()` or
`linuxCncIniPanelLaunchApi.getShellSessionHandoffPackageDisplayViewModel()`
when an outer shell needs the package report compressed into stable read-only
label/value rows for DOM display.
Use `createIniPanelShellSessionHandoffPackageRenderState()` or
`linuxCncIniPanelLaunchApi.getShellSessionHandoffPackageRenderState()` when an
outer shell needs the package display view-model compressed into launch-side
DOM state. The launch page renders `[data-handoff-status]` and
`[data-handoff-rows]` from the package render-state so the first page shows
package-level schema/report status.
Use `createIniPanelShellSessionHandoffPackageMountState()` or
`linuxCncIniPanelLaunchApi.getShellSessionHandoffPackageMountState()` when an
outer shell needs one read-only mount-state that combines package render-state,
the control-page target, required readonly methods, and the current
control-page session readiness snapshot.
Use `createIniPanelShellSessionHandoffPackageMountDisplayViewModel()` or
`linuxCncIniPanelLaunchApi.getShellSessionHandoffPackageMountDisplayViewModel()`
when an outer shell needs mount-state checks as stable read-only label/value
rows. Use `createIniPanelShellSessionHandoffPackageMountRenderState()` or
`linuxCncIniPanelLaunchApi.getShellSessionHandoffPackageMountRenderState()`
when those rows need a status line and `[data-handoff-shell]` style dataset
flags for DOM rendering. Use
`createIniPanelShellSessionHandoffMountDomContract()` or
`linuxCncIniPanelLaunchApi.getShellSessionHandoffMountDomContract()` when an
outer shell needs the required mount/status/rows selectors, mount dataset keys,
row dataset keys, and render-result fields before rendering into shell-owned
DOM. Use `createIniPanelShellSessionHandoffMountDomReadiness()` or
`linuxCncIniPanelLaunchApi.getShellSessionHandoffMountDomReadiness()` to check
that the required document and nodes exist before calling the renderer. Use
`mountIniPanelShellSessionHandoff()` or
`linuxCncIniPanelLaunchApi.mountShellSessionHandoff()` when an outer shell
needs one browser-side workflow result containing the contract, DOM readiness,
mount render-state, renderer result, and any mount error as data. Use
`mountIniPanelShellSessionHandoffFromControlPage()` or
`linuxCncIniPanelLaunchApi.mountShellSessionHandoffFromControlPage()` when an
outer shell has a control-page API or already-observed session-load state and
needs one read-only result containing the derived mount-state, mount display
view-model, mount render-state, and DOM mount workflow result. Use
`readIniPanelShellSessionHandoffStatusFromControlPage()` or
`linuxCncIniPanelLaunchApi.readShellSessionHandoffStatusFromControlPage()`
when an outer shell needs to read the control-page readonly status and verify
the expected handoff status fields as a structured ready/blocked result. Use
`createIniPanelShellSessionHandoffWorkflowSummary()` or
`linuxCncIniPanelLaunchApi.getShellSessionHandoffWorkflowSummary()` when an
outer shell needs one stable end-to-end summary that combines mount workflow,
control-page mount workflow, readonly status result, machine-session
ready/blocked state, and current blocking reasons into display rows. Use
`createIniPanelShellSessionReadinessWorkflowReport()` or
`linuxCncIniPanelLaunchApi.getShellSessionReadinessWorkflowReport()` when an
outer shell needs a machine-readable report for just the session readiness
phase, missing reasons, and stable rows. The workflow overview page exposes
the same report through
`linuxCncIniPanelWorkflowOverviewApi.getWorkflowOverviewSessionReadinessReport()`.
Use `createIniPanelShellApiSurfaceInventory()` or
`linuxCncIniPanelLaunchApi.getShellApiSurfaceInventory()` when an outer shell
needs a machine-readable inventory of launch, control-page, workflow overview,
persistence capabilities, session readiness report rows, and persistence
summary rows. The workflow overview page exposes the same inventory through
`linuxCncIniPanelWorkflowOverviewApi.getWorkflowOverviewApiSurfaceInventory()`.
When provided to `createIniPanelShellSessionHandoffWorkflowSummary()`, the
inventory is also rendered as the read-only `api-surface-inventory` workflow
summary row for browser overview pages and external shells. The same workflow
summary accepts `persistenceSummary` from
`createMachineSessionPersistenceSummary()` and renders it as the
`persistence-summary` row, so a shell overview can show API coverage and
OPFS/session persistence state together.
Use
`createIniPanelShellSessionHandoffWorkflowDisplayViewModel()` or
`linuxCncIniPanelLaunchApi.getShellSessionHandoffWorkflowDisplayViewModel()`
when an outer shell needs that end-to-end summary compressed into stable
`title`, `statusText`, `detailText`, and rows for direct display. Use
`createIniPanelShellSessionHandoffWorkflowRenderState()` or
`linuxCncIniPanelLaunchApi.getShellSessionHandoffWorkflowRenderState()` when an
outer shell needs that workflow display-model compressed into stable
`dataset`, `statusLine`, and rows for direct DOM rendering. Use
`createIniPanelShellSessionHandoffWorkflowDomContract()` or
`linuxCncIniPanelLaunchApi.getShellSessionHandoffWorkflowDomContract()` when an
outer shell needs the required workflow mount/status/rows selectors, mount
dataset keys, and row dataset keys before rendering workflow overview DOM. Use
`createIniPanelShellSessionHandoffWorkflowDomReadiness()` or
`linuxCncIniPanelLaunchApi.getShellSessionHandoffWorkflowDomReadiness()` to
check that those workflow DOM nodes exist before rendering. Use
`renderIniPanelShellSessionHandoffWorkflowState()` or
`linuxCncIniPanelLaunchApi.renderShellSessionHandoffWorkflowState()` when an
outer shell needs the shared read-only DOM renderer for workflow overview rows.
Use `mountIniPanelShellSessionHandoffWorkflow()` or
`linuxCncIniPanelLaunchApi.mountShellSessionHandoffWorkflow()` when an outer
shell needs one structured workflow DOM mount result containing contract,
readiness, render-state, render result, and any mount error as data. Use
`createIniPanelShellWorkflowOverviewContract()` or
`linuxCncIniPanelLaunchApi.getWorkflowOverviewContract()` when an outer shell
needs the `workflow-overview.html` page href, browser API namespace, and method
names before embedding that page. Use
`createIniPanelShellWorkflowOverviewReadiness()` or
`linuxCncIniPanelLaunchApi.getWorkflowOverviewReadiness()` to check whether a
loaded workflow overview iframe exposes the expected read-only API. Use
`mountIniPanelShellWorkflowOverviewEmbedding()` or
`linuxCncIniPanelLaunchApi.mountWorkflowOverviewEmbedding()` when an outer
shell needs one structured embedding result containing contract readiness,
render-state, workflow overview mount result, and any mount error as data. The
workflow overview page also exposes
`linuxCncIniPanelWorkflowOverviewApi.mountWorkflowOverviewEmbedding()` for the
same read-only embedding path from inside the iframe. Use
`createIniPanelShellWorkflowOverviewEmbeddingReport()` or
`linuxCncIniPanelLaunchApi.getWorkflowOverviewEmbeddingReport()` when an outer
shell needs display-ready diagnostics for overview API readiness, missing
methods, embedding readiness, mount phase, render row count, and embedding
errors. The workflow overview page also exposes
`linuxCncIniPanelWorkflowOverviewApi.getWorkflowOverviewEmbeddingReport()` for
the same report shape. Use
`createIniPanelShellWorkflowOverviewEmbeddingDisplayViewModel()` or
`linuxCncIniPanelLaunchApi.getWorkflowOverviewEmbeddingDisplayViewModel()` when
an outer shell needs that report compressed into stable `title`, `statusText`,
`detailText`, and read-only label/value rows for direct rendering. The workflow
overview page also exposes
`linuxCncIniPanelWorkflowOverviewApi.getWorkflowOverviewEmbeddingDisplayViewModel()`.
Use `createIniPanelShellWorkflowOverviewEmbeddingRenderState()` or
`linuxCncIniPanelLaunchApi.getWorkflowOverviewEmbeddingRenderState()` when an
outer shell needs that display view-model compressed into stable `statusLine`,
`dataset`, and rows for a shared DOM renderer. The workflow overview page also
exposes `linuxCncIniPanelWorkflowOverviewApi.getWorkflowOverviewEmbeddingRenderState()`.
Use `createIniPanelShellWorkflowOverviewEmbeddingDomContract()` or
`linuxCncIniPanelLaunchApi.getWorkflowOverviewEmbeddingDomContract()` when an
outer shell needs the required embedding report mount/status/rows selectors,
mount dataset keys, row dataset keys, and render-result fields before adding a
DOM renderer. The workflow overview page also exposes
`linuxCncIniPanelWorkflowOverviewApi.getWorkflowOverviewEmbeddingDomContract()`.
Use `createIniPanelShellWorkflowOverviewEmbeddingDomReadiness()` or
`linuxCncIniPanelLaunchApi.getWorkflowOverviewEmbeddingDomReadiness()` to check
that the required document and embedding report DOM nodes exist before calling
a renderer. The workflow overview page also exposes
`linuxCncIniPanelWorkflowOverviewApi.getWorkflowOverviewEmbeddingDomReadiness()`.
Use `renderIniPanelShellWorkflowOverviewEmbeddingState()` or
`linuxCncIniPanelLaunchApi.renderWorkflowOverviewEmbeddingState()` when an
outer shell needs to render an embedding report render-state into existing
mount/status/rows nodes and receive a structured read-only render result. The
workflow overview page also exposes
`linuxCncIniPanelWorkflowOverviewApi.renderWorkflowOverviewEmbeddingState()`.
Use `mountIniPanelShellWorkflowOverviewEmbeddingState()` or
`linuxCncIniPanelLaunchApi.mountWorkflowOverviewEmbeddingState()` when an outer
shell needs a non-throwing workflow result that combines embedding DOM
readiness, render-state, render result, and any render error as structured
data. The workflow overview page also exposes
`linuxCncIniPanelWorkflowOverviewApi.mountWorkflowOverviewEmbeddingState()`.
Use `createIniPanelShellWorkflowOverviewEmbeddingMountDisplayViewModel()` or
`linuxCncIniPanelLaunchApi.getWorkflowOverviewEmbeddingMountDisplayViewModel()`
when an outer shell needs that mount workflow result compressed into stable
`title`, `statusText`, `detailText`, and read-only label/value rows. The
workflow overview page also exposes
`linuxCncIniPanelWorkflowOverviewApi.getWorkflowOverviewEmbeddingMountDisplayViewModel()`.
Use `createIniPanelShellWorkflowOverviewEmbeddingMountRenderState()` or
`linuxCncIniPanelLaunchApi.getWorkflowOverviewEmbeddingMountRenderState()` when
an outer shell needs that mount display view-model compressed into stable
`statusLine`, `dataset`, and rows for a shared DOM renderer. The workflow
overview page also exposes
`linuxCncIniPanelWorkflowOverviewApi.getWorkflowOverviewEmbeddingMountRenderState()`.
Use `createIniPanelShellWorkflowOverviewEmbeddingMountDomContract()` or
`linuxCncIniPanelLaunchApi.getWorkflowOverviewEmbeddingMountDomContract()` when
an outer shell needs the required mount/status/rows selectors, mount dataset
keys, row dataset keys, and render-result fields before rendering the mount
render-state. The workflow overview page also exposes
`linuxCncIniPanelWorkflowOverviewApi.getWorkflowOverviewEmbeddingMountDomContract()`.
Use `createIniPanelShellWorkflowOverviewEmbeddingMountDomReadiness()` or
`linuxCncIniPanelLaunchApi.getWorkflowOverviewEmbeddingMountDomReadiness()` to
check that the required document and mount render-state DOM nodes exist before
calling a renderer. The workflow overview page also exposes
`linuxCncIniPanelWorkflowOverviewApi.getWorkflowOverviewEmbeddingMountDomReadiness()`.
Use `renderIniPanelShellWorkflowOverviewEmbeddingMountState()` or
`linuxCncIniPanelLaunchApi.renderWorkflowOverviewEmbeddingMountState()` when an
outer shell needs to render a mount render-state into existing mount/status/rows
nodes and receive a structured read-only render result. The workflow overview
page also exposes
`linuxCncIniPanelWorkflowOverviewApi.renderWorkflowOverviewEmbeddingMountState()`.
Use `mountIniPanelShellWorkflowOverviewEmbeddingMountState()` or
`linuxCncIniPanelLaunchApi.mountWorkflowOverviewEmbeddingMountState()` when an
outer shell needs a non-throwing mount workflow result for the mount render-state
DOM. The workflow overview page also exposes
`linuxCncIniPanelWorkflowOverviewApi.mountWorkflowOverviewEmbeddingMountState()`.
Use
`renderIniPanelShellSessionHandoffMountState()` or
`linuxCncIniPanelLaunchApi.renderShellSessionHandoffMountState()` when an outer
shell needs the shared read-only DOM renderer for those rows. The launch page
renders the default waiting mount state into `[data-handoff-mount]`,
`[data-handoff-mount-status]`, and `[data-handoff-mount-rows]` without opening
the control page. The launch page also renders a default workflow overview into
`[data-handoff-workflow]`, `[data-handoff-workflow-status]`, and
`[data-handoff-workflow-rows]`.
The browser workflow smoke
`tests/browser/ini_panel_shell_integration_workflow_smoke.html` verifies the
same external shell handoff: read integration manifest, pass readiness gate,
open the control page target, mount waiting and ready mount-state into an
external shell-owned `[data-external-shell-mount]` DOM region through the shared
mount workflows, then read and validate readonly machine-session status through
the shared status workflow.
The control-page refresh workflow lives in
`runtime/ui/ini-panel/control-page-refresh-workflow.js`. Use
@@ -108,7 +552,8 @@ 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.
read-only readiness snapshot for the embedded panel API, machine-session
ready/blocked state, and first control view.
`createControlPageSessionLoadState()` is the stable state-shape helper for
`waiting-panel`, `waiting-api`, `waiting-session`, `ready`, and `error` phases.
@@ -117,3 +562,7 @@ read-only readiness snapshot for the embedded panel API and first control view.
The entry shell only links pages and renders already-observed panel state. It
does not parse G-code, infer canonical events, or implement LinuxCNC-owned
tool, parameter, planner, or kinematics behavior.
Release validation for this surface is covered by `verify_ui_node_smokes.sh`,
`verify_ini_panel_browser.sh`, and the project handoff docs smoke
`verify_project_release_handoff_docs.sh`.

View File

@@ -0,0 +1,314 @@
# Project release handoff
This is the short project-level handoff for the current LinuxCNC WASM/browser
port scope. It points a new maintainer to the supported workflow surfaces, the
required release gates, and the boundaries that must remain blocked until real
LinuxCNC-owned runtime proof exists.
## Start here
Use these documents as the current handoff set:
- `README.md` for the workspace layout and release validation commands.
- `docs/panel-entry.md` for browser/UI entry points and shell handoff helpers.
- `runtime/sdk/README.md` for stable SDK imports from `runtime/sdk/src/index.js`.
- `docs/opfs-session-persistence.md` for OPFS/session persistence scope.
- `docs/real-browser-simulation-priority.md` for the standing first priority:
build the real browser CNC simulation page before lower-value metadata work.
- `docs/sim-configs-coverage-handoff.md` for sim-config inventory status.
- `docs/host-runtime-boundary-handoff.md` for blocked runtime families.
- `docs/source-reuse-map.md` and `docs/drift-report.md` for LinuxCNC source
reuse and non-drift rules.
- `../PROJECT_COMPLETION_TRACKER.md` for project-level acceptance tracking.
Old turn logs `text1.txt` through `text14.txt` are historical context. Current
continuation records are in `../text15.txt`.
## Supported current workflows
## First Priority Constraint
Subsequent work must prioritize a real browser CNC simulation page. The page
should converge on visible machine/session loading, G-code program state,
LinuxCNC-backed interpreter execution, machine readiness/status panels, and a
toolpath/preview area backed by LinuxCNC-produced output or validated runtime
events. The authoritative constraint is
`docs/real-browser-simulation-priority.md`.
Its executable docs gate is
`verify_real_browser_simulation_priority_docs.sh`, which is included in the
host smoke and project release gate and ends with
`real_browser_simulation_priority_docs_node_smoke=ok`.
The first implementation entry point is `runtime/ui/simulation/index.html`,
validated by `verify_real_simulation_browser.sh`, which ends with
`browser_real_simulation_page_smoke=ok`.
This priority does not permit JavaScript-owned CNC semantics. G-code, tool,
parameter, kinematics, remap, planner, and canonical motion behavior must remain
owned by vendored LinuxCNC source and existing LinuxCNC-backed WASM boundaries.
- INI panel launch, edit/run page, read-only control page, workflow overview,
and external shell handoff are exposed from `runtime/ui/ini-panel/`.
- External shells should use the read-only helpers documented in
`docs/panel-entry.md`, including workflow overview embedding mount DOM
contract/readiness/renderer/wrapper helpers.
- SDK callers should import stable helpers from `runtime/sdk/src/index.js`,
including `createIniPanelShellApiSurfaceInventory()` for API discovery and
`createIniPanelShellSessionReadinessWorkflowReport()` when they need
machine-readable session readiness phase and missing reasons. The workflow
overview exposes the API surface inventory as a read-only summary row.
- CI dashboards and external SDK callers can use
`createProjectReleaseGateManifest()` to discover required gate commands and
expected smoke outputs, `createProjectReleaseGateResultMatrix()` to map
observed outputs onto passed/unknown gate rows,
`createProjectReleaseGateActionPlan()` to derive pending gate commands, the
next command, display rows, and a shell script from that matrix,
`createProjectReleaseGateExecutionManifest()` to package each gate command,
expected output, observed-output evidence, and passed/unknown status,
`createProjectReleaseGateExecutionSummaryViewModel()` to expose dashboard
status text, evidence counts, next missing gate, and normalized per-gate rows,
and
`createProjectReleaseReadinessReport()` for a machine-readable release
readiness report covering the same manifest, execution manifest, result
matrix, action plan, sim-config inventory baseline, and blocked runtime
families.
`createProjectReleaseReadinessSummaryViewModel()` turns that report into
stable status text and rows for dashboards. The report helper is evidence
driven and only reports `ready: true` when the caller supplies release gate
evidence such as `project_release_gate=ok`. The project release gate writes
the same report to `build/project-release-readiness.json` and validates it
with `verify_project_release_readiness_artifact.sh`.
External tools can load that artifact with
`parseProjectReleaseReadinessArtifactJson()` and validate it with
`createProjectReleaseReadinessArtifactValidation()`. Artifact validation
requires the embedded gate manifest, execution manifest, result matrix, and
action plan to be ready, so stale release JSON cannot pass the executable
artifact gate.
`createProjectReleaseReadinessArtifactValidationSummaryViewModel()` turns the
validation into stable dashboard rows without reinterpreting artifact fields.
`createProjectReleaseReadinessArtifactValidationActionPlan()` turns a blocked
artifact validation into the project release gate and artifact validation
commands to run next, plus the missing evidence rows.
`createProjectReleaseReadinessArtifactJsonWorkflow()` is the one-call
non-throwing JSON text workflow for dashboards that need parse, validation,
summary, and action-plan output from the readiness artifact.
`loadProjectReleaseReadinessArtifactUrlWorkflow()` provides the same
SDK-side workflow from an artifact URL and caller-provided fetch
implementation, including fetch status and next-command output.
`createProjectReleaseReadinessArtifactUrlWorkflowSummaryViewModel()` turns
that SDK URL workflow into stable dashboard rows and status text.
`createProjectReleaseReadinessArtifactUrlWorkflowActionPlan()` turns blocked
URL workflows into ordered input, fetch, and command actions for CI tooling.
`createProjectBatchAcceptanceCapabilityMatrix()` classifies per-batch evidence
into accepted API/workflow/gate/browser capabilities and rejected entries.
`createProjectBatchAcceptanceWorkflow()` records the per-batch acceptance rule
that each batch must ship at least one callable API, verifiable workflow,
executable gate, or browser capability. Its summary view-model and action-plan
helpers provide dashboard rows and the next batch-acceptance gate command.
`createProjectBatchAcceptanceChecklist()` combines those results into
pass/blocked checklist rows for external release dashboards.
`createProjectBatchAcceptanceReport()` packages the matrix, workflow,
summary, action plan, and checklist into one stable CI/dashboard object.
`createProjectBatchAcceptanceReportValidation()` and
`createProjectBatchAcceptanceReportJsonWorkflow()` validate that object from
parsed data or JSON text without throwing.
`createProjectBatchAcceptanceReportValidationSummaryViewModel()` and
`createProjectBatchAcceptanceReportValidationActionPlan()` expose dashboard
rows and the next artifact gate command for blocked report validation.
`loadProjectBatchAcceptanceReportUrlWorkflow()` fetches hosted batch
acceptance report JSON through a caller-provided `fetch` implementation, then
runs the same validation workflow without throwing.
`createProjectBatchAcceptanceReportUrlWorkflowSummaryViewModel()` and
`createProjectBatchAcceptanceReportUrlWorkflowActionPlan()` expose dashboard
rows and ordered input/fetch/gate actions for that URL workflow.
The executable batch acceptance gate writes
`build/project-batch-acceptance.json` with
`write_project_batch_acceptance_artifact.mjs` and validates it through
`verify_project_batch_acceptance_artifact.sh`, ending with
`project_batch_acceptance_artifact_node_smoke=ok`.
Workflow overview shells can use
`getWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryViewModel()`
and
`getWorkflowOverviewReleaseReadinessArtifactUrlGateExecutionSummaryViewModel()`
to display the embedded release gate execution summary without parsing
artifact internals. They can render or mount those per-gate rows through
`getWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryRenderState()`,
`getWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryDomContract()`,
`getWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryDomReadiness()`,
`renderWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryState()`, and
`mountWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryState()`, and
`getWorkflowOverviewReleaseReadinessArtifactUrlWorkflowActionPlan()` to show
the URL workflow next action or command without executing gates. Direct JSON
validation callers can use
`getWorkflowOverviewReleaseReadinessArtifactValidationActionPlan()` for the
artifact-validation next-command plan. They can render or mount that action
plan through
`getWorkflowOverviewReleaseReadinessArtifactValidationActionPlanRenderState()`,
`getWorkflowOverviewReleaseReadinessArtifactValidationActionPlanDomContract()`,
`getWorkflowOverviewReleaseReadinessArtifactValidationActionPlanDomReadiness()`,
`renderWorkflowOverviewReleaseReadinessArtifactValidationActionPlanState()`, and
`mountWorkflowOverviewReleaseReadinessArtifactValidationActionPlanState()`.
Browser shells can also pass the JSON to
`linuxCncIniPanelWorkflowOverviewApi.validateWorkflowOverviewReleaseReadinessArtifactJson()`
through the workflow overview iframe and receive stable validation rows. They
can call `runWorkflowOverviewReleaseReadinessArtifactJsonWorkflow()` when they
need parse, validation, summary, action-plan, and gate execution summary
output in one workflow result, and
`getWorkflowOverviewReleaseReadinessArtifactJsonWorkflowSummaryViewModel()`
when they need stable dashboard rows for that JSON workflow. The JSON
workflow action plan is available through
`getWorkflowOverviewReleaseReadinessArtifactJsonWorkflowActionPlan()`. They
can render or mount the JSON workflow state into caller-owned DOM through
`getWorkflowOverviewReleaseReadinessArtifactJsonWorkflowDomContract()`,
`getWorkflowOverviewReleaseReadinessArtifactJsonWorkflowDomReadiness()`,
`renderWorkflowOverviewReleaseReadinessArtifactJsonWorkflowState()`, and
`mountWorkflowOverviewReleaseReadinessArtifactJsonWorkflowState()`. They
can also call
`linuxCncIniPanelWorkflowOverviewApi.loadWorkflowOverviewReleaseReadinessArtifactUrl()`
to fetch the artifact URL, validate it, and optionally mount the resulting
read-only render state. That URL workflow includes a validation summary
view-model and can be summarized with
`getWorkflowOverviewReleaseReadinessArtifactUrlWorkflowSummaryViewModel()`.
The same workflow can expose the ordered next-action plan through
`getWorkflowOverviewReleaseReadinessArtifactUrlWorkflowActionPlan()`. Shells
can render or mount the URL workflow state through
`getWorkflowOverviewReleaseReadinessArtifactUrlWorkflowRenderState()`,
`getWorkflowOverviewReleaseReadinessArtifactUrlWorkflowDomContract()`,
`getWorkflowOverviewReleaseReadinessArtifactUrlWorkflowDomReadiness()`,
`renderWorkflowOverviewReleaseReadinessArtifactUrlWorkflowState()`, and
`mountWorkflowOverviewReleaseReadinessArtifactUrlWorkflowState()`.
Direct artifact JSON validation can expose its own next-command plan through
`getWorkflowOverviewReleaseReadinessArtifactValidationActionPlan()`.
When the outer shell needs DOM output, the workflow
overview iframe also exposes
display, render-state, DOM readiness, renderer, and mount helpers for the
same release readiness artifact validation.
- OPFS/session callers can use `createMachineSessionPersistenceSummary()` or
`linuxCncIniPanelApi.getMachineSessionPersistenceSummary()` to inspect
machine files, session snapshot, session readiness, session load, readonly
status, and shell handoff readiness as stable rows. Shell handoff workflow
summaries can include that report as the `persistence-summary` row beside the
API surface inventory row, and launch/workflow-overview APIs expose the same
helper for external shells. The summary also has display and render-state
helpers for caller-owned DOM.
- OPFS/session persistence also has a dedicated browser workflow gate,
`verify_opfs_session_workflow_browser.sh`, covering save machine files, save
G-code, save session snapshot, session readiness, load into WASM, and
persistence summary output.
- OPFS/session persistence is host-side storage glue. Parameter and tool-table
behavior still comes from the LinuxCNC-backed interpreter SDK calls.
- Sim-config inventory promotion remains evidence-driven and must keep
`unexpected_fail=0`.
## Required release gates
Run the single project release gate before claiming the current scope is
release-ready:
```bash
wasm-port/tests/host/verify_project_release_gate.sh
```
It executes the same minimum release validation commands:
```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
SKIP_INI_BUILD=1 SKIP_INTERP_BUILD=1 wasm-port/tests/browser/verify_opfs_session_workflow_browser.sh
SKIP_INI_BUILD=1 SKIP_INTERP_BUILD=1 wasm-port/tests/browser/verify_release_artifact_url_workflow_browser.sh
wasm-port/tests/sdk/node/verify_project_batch_acceptance_workflow.sh
wasm-port/tests/ui/node/verify_ui_node_smokes.sh
wasm-port/tests/sdk/node/verify_project_release_artifact_url_workflow.sh
wasm-port/tests/host/verify_host_smokes.sh
```
The host aggregate gate includes SDK, OPFS, docs, UI Node, WASM Node, and
browser smoke coverage. The project release gate then writes and verifies a
machine-readable release readiness artifact:
```text
project_release_readiness_artifact=...
project_release_readiness_artifact_node_smoke=ok
```
A passing aggregate ends with:
```text
host_wasm_opfs_browser_smokes=ok
project_release_gate=ok
```
## Expected baseline outputs
The current sim-config inventory release baseline is:
```text
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
```
The expected docs and SDK smoke outputs include:
```text
project_release_handoff_docs_node_smoke=ok
opfs_session_docs_node_smoke=ok
sim_configs_coverage_docs_node_smoke=ok
host_runtime_boundary_docs_node_smoke=ok
sdk_surface_node_smoke=ok
project_batch_acceptance_workflow_node_smoke=ok
project_release_artifact_url_workflow_node_smoke=ok
```
The expected browser INI panel smoke outputs include:
```text
browser_ini_opfs_smoke=ok
browser_ini_control_page_smoke=ok
browser_ini_launch_smoke=ok
browser_ini_workflow_overview_smoke=ok
browser_ini_shell_integration_workflow_smoke=ok
browser_opfs_session_workflow_smoke=ok
browser_release_artifact_url_workflow_smoke=ok
```
## Blocked runtime families
These runtime families remain blocked:
- `L4-USER-M-PROCESS`
- `L4-TOOL-DB`
- `L4-PYTHON-REMAP`
Do not promote them from skipped/blocked state without LinuxCNC-owned native
runtime proof followed by Node/WASM and browser/host validation. The opt-in
runtime probes are documented in `docs/host-runtime-boundary-handoff.md`; they
must not be run on hosts that do not provide the required LinuxCNC runtime.
## Acceptance checklist
- `git diff --check` passes.
- Vendor sync and standalone CNC semantic guards pass.
- Interpreter WASM Node smoke passes.
- Sim-config inventory has `unexpected_fail=0`.
- UI Node smoke passes.
- Browser INI panel smoke passes.
- Release artifact URL browser workflow smoke passes.
- Host aggregate smoke passes.
- `verify_project_release_gate.sh` passes.
- SDK exports match `runtime/sdk/README.md`.
- `docs/panel-entry.md` documents the external shell workflow surface.
- OPFS/session, sim-config coverage, host/runtime boundary, source reuse, and
drift docs all point to the current validation gates.
- Blocked runtime families remain documented and are not falsely promoted.
## Next maintainer rule
Add new CNC behavior only by reusing vendored LinuxCNC source or a documented
runtime-edge adapter. JavaScript may stage files, mount UI state, manage OPFS,
load WASM modules, and call C ABI functions; it must not implement G-code,
tool, parameter, planner, kinematics, remap, or canonical motion semantics.

View File

@@ -0,0 +1,82 @@
# Real Browser Simulation Priority
This document is a standing project constraint for all work after 2026-06-16.
## Primary Objective
The first priority is to deliver a real browser CNC simulation page, not more
project metadata or dashboard-only helpers. Future batches should prefer
visible, interactive browser capability that moves the product toward an
operator-usable CNC simulation surface.
The target page must become a real CNC simulation page with:
- a machine/session loader backed by the existing OPFS/session workflow;
- a G-code program view backed by persisted or staged program text;
- LinuxCNC-backed interpreter execution through the existing WASM SDK;
- machine state panels for run status, active program, machine files, and
missing readiness;
- a toolpath/preview area driven by LinuxCNC-produced interpreter/canonical
output or existing validated runtime events, not JavaScript-owned CNC
semantics;
- browser smoke coverage that proves the page loads, renders nonempty runtime
state, and can run a representative LinuxCNC-backed program flow.
The first implementation entry point is:
```text
runtime/ui/simulation/index.html
```
Its browser gate is:
```bash
wasm-port/tests/browser/verify_real_simulation_browser.sh
```
## Batch Priority
Unless a user explicitly changes priority, choose work in this order:
1. Real UI/browser simulation page capability.
2. Browser workflow smoke for that simulation capability.
3. UI APIs needed by the simulation page.
4. OPFS/session integration needed by the simulation page.
5. SDK/API helpers only when they directly unblock the simulation page.
6. Documentation or release metadata only when it creates an executable gate or
protects the simulation-page direction.
Every implementation batch still must produce at least one callable API,
verifiable workflow, executable gate, or real UI/browser capability.
## Boundary Rules
The browser simulation page must not implement CNC semantics in JavaScript.
G-code interpretation, canonical motion behavior, tool semantics, parameter
semantics, kinematics, remap behavior, and planner behavior must remain owned by
vendored LinuxCNC source and existing LinuxCNC-backed WASM C ABI boundaries.
JavaScript/browser code may own:
- layout and interaction state;
- OPFS/session persistence;
- file staging into the Emscripten filesystem;
- calls into the SDK/WASM boundary;
- rendering LinuxCNC-produced output, canonical events, and validated runtime
summaries;
- non-CNC UI state such as selected file, active tab, viewport mode, and
validation messages.
## Required Evidence
The priority is enforced by:
```bash
wasm-port/tests/docs/node/verify_real_browser_simulation_priority_docs.sh
wasm-port/tests/host/verify_host_smokes.sh
wasm-port/tests/host/verify_project_release_gate.sh
```
The docs gate must verify that this priority document, the release handoff, the
README, and `PROJECT_COMPLETION_TRACKER.md` all point future work toward a real
browser CNC simulation page while preserving the LinuxCNC semantic boundary.

View File

@@ -0,0 +1,122 @@
# Sim-config coverage release handoff
This document is the short release-gate handoff for
`linuxcnc/configs/sim` coverage in the LinuxCNC WASM/browser port. It points to
the detailed matrix, fixes the current machine-readable inventory baseline, and
records which skipped rows remain blocked instead of promoted.
Detailed references:
- `docs/sim-configs-coverage-matrix.md`
- `docs/sim-configs-completion-plan.md`
- `docs/full-process-boundary-design.md`
- `tests/wasm/node/verify_sim_configs_inventory_wasm.mjs`
## Current inventory baseline
The current Node/WASM inventory gate is:
```bash
SKIP_INTERP_BUILD=1 wasm-port/tests/wasm/node/verify_sim_configs_inventory_wasm.sh
```
Required output:
```text
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
sim_configs_wasm_node_inventory_skip_ASSET_ONLY=65
sim_configs_wasm_node_inventory_skip_L4_PYTHON_REMAP=53
sim_configs_wasm_node_inventory_skip_L4_TOOL_DB=1
sim_configs_wasm_node_inventory_skip_L4_USER_M_PROCESS=1
sim_configs_wasm_node_inventory_skip_NON_MAIN_CLASS=10
sim_configs_wasm_node_inventory_skip_UPSTREAM_DEMO=1
sim_configs_wasm_node_inventory=ok
```
The generated artifact baseline is:
- `build/wasm/sim-configs-inventory/summary.tsv`
- `build/wasm/sim-configs-inventory/skip-summary.tsv`
- `build/wasm/sim-configs-inventory/boundary-summary.tsv`
- `build/wasm/sim-configs-inventory/blocked-dependency-summary.tsv`
- `build/wasm/sim-configs-inventory/full-process-boundary-summary.tsv`
- `build/wasm/sim-configs-inventory/python-remap-boundary-summary.tsv`
- `build/wasm/sim-configs-inventory/runtime-boundary-promotion-blockers.tsv`
- `build/wasm/sim-configs-inventory/runtime-boundary-host-readiness-rollup.tsv`
- `build/wasm/sim-configs-inventory/runtime-boundary-opt-in-probe-dispatch-rollup.tsv`
## Skip reason map
The TSV artifacts use hyphenated reason names. The shell output uses
underscore-safe metric names. Treat these as the same baseline:
| Artifact reason | Shell metric suffix | Count | Release decision |
| --- | --- | ---: | --- |
| `ASSET-ONLY` | `ASSET_ONLY` | 65 | Subroutine/support asset; not a standalone browser main-program target. |
| `L4-PYTHON-REMAP` | `L4_PYTHON_REMAP` | 53 | Keep inventory-only until a LinuxCNC-owned Python-remap runtime proof exists. |
| `L4-TOOL-DB` | `L4_TOOL_DB` | 1 | Keep blocked until a LinuxCNC-owned tool database process/protocol proof exists. |
| `L4-USER-M-PROCESS` | `L4_USER_M_PROCESS` | 1 | Keep blocked until a LinuxCNC-owned external user-M process state proof exists. |
| `NON_MAIN_CLASS` | `NON_MAIN_CLASS` | 10 | Covered by class representative or macro/load policy, not direct standalone promotion. |
| `UPSTREAM-DEMO` | `UPSTREAM_DEMO` | 1 | Preserved upstream demo edge; do not force pass. |
## Promotion policy
Rows may only move from skip/inventory to promoted coverage when all of these
are true:
1. The row has a LinuxCNC-owned runtime proof for the behavior being promoted.
2. Native proof passes first.
3. Node/WASM proof passes after native proof.
4. Browser/host proof passes after Node/WASM proof.
5. `verify_no_standalone_cnc_semantics.sh` still passes.
6. Generated inventory artifacts and docs agree on the new status.
Do not promote these runtime families from JavaScript glue alone:
- `L4-PYTHON-REMAP`
- `L4-TOOL-DB`
- `L4-USER-M-PROCESS`
## Release gate
Use this gate when changing sim-config coverage, promotion state, or inventory
documentation:
```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_sim_configs_inventory_wasm.sh
wasm-port/tests/docs/node/verify_sim_configs_coverage_docs.sh
wasm-port/tests/host/verify_host_smokes.sh
```
The host aggregate must continue to include:
```text
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
opfs_session_docs_node_smoke=ok
sim_configs_coverage_docs_node_smoke=ok
host_wasm_opfs_browser_smokes=ok
```
## Blocked runtime probes
The opt-in runtime probes remain disabled on this host unless the required
LinuxCNC runtime processes are available:
```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
```
These commands are proof gates for future work, not part of the current
release gate. The current inventory must continue to record the corresponding
families as blocked.

View File

@@ -29,6 +29,11 @@ Current validation is intentionally mechanical:
documented Python/remap runtime-edge stubs in
`runtime/core/linuxcnc_wrap/linuxcnc_interp_edge_stubs.cpp`.
The project-level release handoff in `docs/project-release-handoff.md` links
this source reuse map to the README, panel entry documentation, SDK surface,
OPFS/session release gate, sim-config coverage gate, host/runtime blocked
families, and drift report.
## Reuse Matrix
| Capability | LinuxCNC source files | Port classification | Standalone boundary | Current validation |

View File

@@ -0,0 +1,103 @@
import { loadTextFile } from "./file-service.js";
import {
gcodeProgramPath,
machineIniPath,
normalizeOpfsPath,
parameterFilePath,
sessionSnapshotPath,
toolTablePath,
} from "./path-model.js";
import { loadMachineSessionSnapshot } from "./snapshot-store.js";
function checkResult(name, path, ok, error = null) {
return {
name,
path,
ok,
error,
};
}
async function checkTextFile(name, path, storage) {
try {
const text = await loadTextFile(path, storage);
return {
...checkResult(name, path, true),
bytes: text.length,
};
} catch (error) {
return checkResult(name, path, false, error.message);
}
}
function resolveReadinessPaths(machineId, options = {}) {
const paths = {
ini: options.iniOpfsPath ?? machineIniPath(machineId, options.iniFilename),
parameters:
options.parameterOpfsPath ?? parameterFilePath(machineId, options.parameterFilename),
toolTable:
options.toolTableOpfsPath ?? toolTablePath(machineId, options.toolTableFilename),
};
if (options.gcodeOpfsPath !== undefined) {
paths.gcode = normalizeOpfsPath(options.gcodeOpfsPath);
} else if (options.gcodeFilename !== undefined) {
paths.gcode = gcodeProgramPath(options.gcodeFilename);
}
if (options.sessionId !== undefined) {
paths.snapshot = sessionSnapshotPath(options.sessionId, options.snapshotFilename);
}
return paths;
}
function summarizeReadiness(checks, snapshotResult) {
const failed = checks.filter((check) => !check.ok);
const snapshotFailed = snapshotResult && !snapshotResult.ok ? [snapshotResult] : [];
return [...failed, ...snapshotFailed];
}
export async function readMachineSessionReadiness(machineId, options = {}) {
const paths = resolveReadinessPaths(machineId, options);
const checks = [
await checkTextFile("ini", paths.ini, options.storage),
await checkTextFile("parameters", paths.parameters, options.storage),
await checkTextFile("toolTable", paths.toolTable, options.storage),
];
if (options.gcodeRequired === true || paths.gcode !== undefined) {
checks.push(await checkTextFile("gcode", paths.gcode, options.storage));
}
let snapshot = null;
let snapshotCheck = null;
if (options.sessionId !== undefined) {
try {
snapshot = await loadMachineSessionSnapshot(options.sessionId, machineId, {
storage: options.storage,
filename: options.snapshotFilename,
});
snapshotCheck = checkResult("snapshot", paths.snapshot, true);
} catch (error) {
snapshotCheck = checkResult("snapshot", paths.snapshot, false, error.message);
}
}
const failures = summarizeReadiness(checks, snapshotCheck);
return {
machineId,
ready: failures.length === 0,
phase: failures.length === 0 ? "ready" : "blocked",
paths,
checks,
snapshotCheck,
snapshot,
missing: failures.map((failure) => failure.name),
errors: failures.map((failure) => ({
name: failure.name,
path: failure.path,
error: failure.error,
})),
};
}

View File

@@ -23,11 +23,128 @@ Use `src/index.js` for stable imports:
```js
import {
INI_PANEL_ENTRIES,
analyzeIniRuntimeBoundaries,
createControlPageBrowserApiManifest,
createControlPageBrowserApiMethods,
createControlPageBrowserApiSchema,
createIniPanelLaunchApiManifest,
createIniPanelLauncherLinkViewModel,
createIniPanelShellApiSurfaceInventory,
createIniPanelShellControlPageContract,
createIniPanelShellIntegrationManifest,
createIniPanelShellIntegrationReadiness,
createIniPanelShellIntegrationSchema,
createIniPanelShellIntegrationWorkflowPlan,
createIniPanelShellSessionReadinessWorkflowReport,
createIniPanelShellViewModel,
createIniPanelShellWorkflowOverviewContract,
createIniPanelShellWorkflowOverviewEmbeddingMountDomContract,
createIniPanelShellWorkflowOverviewEmbeddingMountDomReadiness,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactDisplayViewModel,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactDomContract,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactDomReadiness,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryDomContract,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryDomReadiness,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryRenderState,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryViewModel,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflowActionPlan,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflowDomContract,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflowDomReadiness,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflowRenderState,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflowSummaryViewModel,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactRenderState,
runIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflow,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlGateExecutionSummaryViewModel,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowActionPlan,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowDomContract,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowDomReadiness,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowRenderState,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowSummaryViewModel,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationActionPlan,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationActionPlanDomContract,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationActionPlanDomReadiness,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationActionPlanRenderState,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationSummaryViewModel,
createLinuxCncIniSdk,
createLinuxCncInterpSdk,
createMachineSessionPersistenceDisplayViewModel,
createMachineSessionPersistenceRenderState,
createMachineSessionPersistenceSummary,
createMachineSessionSnapshotPayload,
createProjectReleaseGateActionPlan,
createProjectReleaseGateExecutionManifest,
createProjectReleaseGateExecutionSummaryViewModel,
createProjectReleaseGateManifest,
createProjectReleaseGateResultMatrix,
createProjectReleaseReadinessArtifactJsonWorkflow,
createProjectReleaseReadinessArtifactUrlWorkflowActionPlan,
createProjectReleaseReadinessArtifactUrlWorkflowSummaryViewModel,
createProjectReleaseReadinessArtifactValidation,
createProjectReleaseReadinessArtifactValidationActionPlan,
createProjectReleaseReadinessArtifactValidationSummaryViewModel,
createProjectReleaseReadinessReport,
createProjectReleaseReadinessSummaryViewModel,
createSessionSnapshot,
defaultMachinePaths,
gcodeFilenameFromProgramPath,
gcodeProgramPath,
getIniPanelEntryByHref,
getIniPanelEntryManifest,
getOpfsRoot,
getVisibleIniPanelEntries,
isSupportedIniPanelControlPageApiManifest,
isSupportedIniPanelLaunchApiManifest,
isSupportedIniPanelShellIntegrationManifest,
loadIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrl,
loadProjectReleaseReadinessArtifactUrlWorkflow,
loadMachineSessionFromOpfs,
loadMachineSessionSnapshot,
loadMachineTextFiles,
loadMachineToolTableFromOpfs,
loadTextFile,
machineFilePaths,
machineIniPath,
mountIniPanelShellWorkflowOverviewEmbeddingMountState,
mountIniPanelShellWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryState,
mountIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflowState,
mountIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowState,
mountIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationActionPlanState,
mountIniPanelShellWorkflowOverviewReleaseReadinessArtifactState,
normalizeOpfsPath,
parameterFilePath,
parseProjectReleaseReadinessArtifactJson,
planIniFileContextStaging,
planSimConfigStaging,
createProjectBatchAcceptanceActionPlan,
createProjectBatchAcceptanceCapabilityMatrix,
createProjectBatchAcceptanceChecklist,
createProjectBatchAcceptanceReport,
createProjectBatchAcceptanceReportJsonWorkflow,
createProjectBatchAcceptanceReportUrlWorkflowActionPlan,
createProjectBatchAcceptanceReportUrlWorkflowSummaryViewModel,
createProjectBatchAcceptanceReportValidationActionPlan,
createProjectBatchAcceptanceReportValidationSummaryViewModel,
createProjectBatchAcceptanceReportValidation,
createProjectBatchAcceptanceSummaryViewModel,
createProjectBatchAcceptanceWorkflow,
loadProjectBatchAcceptanceReportUrlWorkflow,
readMachineSessionReadiness,
renderIniPanelShellWorkflowOverviewEmbeddingMountState,
renderIniPanelShellWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryState,
renderIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflowState,
renderIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowState,
renderIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationActionPlanState,
renderIniPanelShellWorkflowOverviewReleaseReadinessArtifactState,
restoreMachineParametersFromOpfs,
saveMachineSessionSnapshot,
saveMachineTextFiles,
saveSessionSnapshot,
saveTextFile,
sessionSnapshotPath,
toolTablePath,
validateIniPanelShellWorkflowOverviewReleaseReadinessArtifactJson,
validateSessionSnapshot,
} from "./src/index.js";
```
@@ -123,3 +240,258 @@ change interpreter semantics.
path but keeps the runner loop going after LinuxCNC reports an error, matching
upstream `rs274 -n 0` regression tests such as `tests/interp/oword-unwind`.
It does not implement or reinterpret LinuxCNC error semantics.
## Project release readiness export
`createProjectReleaseGateManifest()` returns the required release gate IDs,
commands, expected smoke outputs, and gate count for CI dashboards and external
SDK callers. `createProjectReleaseGateResultMatrix()` maps observed gate output
and explicit gate results onto that manifest as stable `passed`/`unknown` rows.
`createProjectReleaseGateActionPlan()` turns that matrix into pending gate
commands, the next gate command, stable display rows, and a shell script that
external CI can execute or show without duplicating the manifest order.
`createProjectReleaseGateExecutionManifest()` packages the same gate manifest
with caller-provided explicit results and observed output evidence. Each row
includes the command, expected output, matched observed output when present,
evidence kind, and `passed`/`unknown` status so CI or browser dashboards can
render release-gate execution state without scraping shell scripts.
`createProjectReleaseGateExecutionSummaryViewModel()` turns that execution
manifest into stable dashboard status text, evidence counts, next missing gate,
summary rows, and normalized per-gate rows.
`createProjectReleaseReadinessReport()` returns a machine-readable release
readiness report that embeds the same manifest, result matrix, action plan,
gate execution manifest, gate execution summary view-model, sim-config
inventory baseline, and blocked runtime families. It is evidence driven: the
default report is `ready: false` until the caller supplies observed gate output
such as `project_release_gate=ok` or explicit gate results.
`createProjectReleaseReadinessSummaryViewModel()` turns the report into stable
status text, a status line, and display rows for dashboards without executing
any gate.
`createProjectBatchAcceptanceCapabilityMatrix()` normalizes per-batch evidence
and classifies accepted `api`, `workflow`, `gate`, and `browser` capabilities
separately from rejected docs-only or unknown entries.
`createProjectBatchAcceptanceWorkflow()` packages that capability matrix with
release-gate evidence for the project rule that each batch must produce at
least one callable API, verifiable workflow, executable gate, or browser
capability. The companion
`createProjectBatchAcceptanceSummaryViewModel()` and
`createProjectBatchAcceptanceActionPlan()` helpers make that evidence
dashboard-friendly and provide the next gate command when the batch acceptance
smoke has not been observed. `createProjectBatchAcceptanceChecklist()` combines
the capability matrix, gate evidence, and action plan into pass/blocked rows
for release dashboards. `createProjectBatchAcceptanceReport()` packages the
matrix, workflow, summary, action plan, and checklist into one stable object
for external CI or release dashboards.
`createProjectBatchAcceptanceReportValidation()` validates that report shape
without copying test assertions.
`createProjectBatchAcceptanceReportValidationSummaryViewModel()` and
`createProjectBatchAcceptanceReportValidationActionPlan()` expose dashboard rows
and the next artifact gate command for blocked report validation.
`createProjectBatchAcceptanceReportJsonWorkflow()` is the non-throwing JSON
workflow for external CI systems that exchange batch acceptance reports as
strings.
`loadProjectBatchAcceptanceReportUrlWorkflow()` is the fetch-driven URL
workflow for hosted batch acceptance reports. It returns fetch status, the JSON
workflow, report validation, and missing evidence without throwing.
`createProjectBatchAcceptanceReportUrlWorkflowSummaryViewModel()` and
`createProjectBatchAcceptanceReportUrlWorkflowActionPlan()` turn the same URL
workflow into dashboard rows and ordered input/fetch/gate actions.
This helper does not execute shell commands and does not inspect or implement
CNC behavior. It only packages project-level release evidence and blocked
runtime policy that are already documented in `docs/project-release-handoff.md`
and `../PROJECT_COMPLETION_TRACKER.md`.
The executable release gate uses the same helper to write
`build/project-release-readiness.json` after all project checks pass, then
validates that artifact with
`tests/host/verify_project_release_readiness_artifact.sh`. External tools can
load that JSON with `parseProjectReleaseReadinessArtifactJson()` and validate
it with `createProjectReleaseReadinessArtifactValidation()` without copying the
host test assertions. The validation result includes `expectedGateCount` and
`expectedGateIds` so external callers can compare an artifact against the
current manifest, plus `gateManifestReady`, `gateExecutionManifestReady`,
`gateExecutionSummaryReady`, `gateResultMatrixReady`, and
`gateActionPlanReady` so stale artifacts that omit the embedded gate evidence
chain fail the same executable gate.
`createProjectReleaseReadinessArtifactValidationSummaryViewModel()` turns that
validation result into stable status text and display rows for dashboards or
browser shells without reinterpreting the artifact fields.
`createProjectReleaseReadinessArtifactValidationActionPlan()` turns a blocked
artifact validation result into the exact project release gate and artifact
validation commands to run next, plus the missing evidence list and shell
script. Ready validations return no commands.
`createProjectReleaseReadinessArtifactJsonWorkflow()` is the non-throwing
one-call wrapper for dashboards that receive artifact JSON text. It parses,
validates, builds the validation summary, and builds the action plan. Invalid
JSON returns a blocked workflow with `parsed: false` and the same next-command
action plan instead of throwing.
`loadProjectReleaseReadinessArtifactUrlWorkflow()` is the SDK-side URL workflow
for callers that supply a `fetch` implementation. It fetches the artifact URL,
runs the JSON workflow, and returns fetch status, validation, summary, and
action-plan output without executing release gates.
`createProjectReleaseReadinessArtifactUrlWorkflowSummaryViewModel()` turns the
URL workflow result into stable dashboard status text and rows for ready,
missing-input, fetch-failure, and artifact-validation-blocked states.
`createProjectReleaseReadinessArtifactUrlWorkflowActionPlan()` turns the same
URL workflow into ordered workflow-level actions, distinguishing caller input,
fetch checks, and executable gate commands while preserving a shell script for
command actions.
`tests/sdk/node/verify_project_release_artifact_url_workflow.sh` is the
dedicated executable SDK gate for that URL workflow. It verifies caller-provided
fetch handling, ready summaries, action plans, missing input, HTTP failure,
thrown fetch errors, and ends with
`project_release_artifact_url_workflow_node_smoke=ok`.
`tests/sdk/node/verify_project_batch_acceptance_workflow.sh` is the executable
SDK gate for the per-batch acceptance workflow. It runs the workflow smoke,
writes `build/project-batch-acceptance.json` through
`write_project_batch_acceptance_artifact.mjs`, validates that artifact through
`verify_project_batch_acceptance_artifact.sh`, and emits
`project_batch_acceptance_workflow_node_smoke=ok` before ending with
`project_batch_acceptance_artifact_node_smoke=ok`.
## OPFS/session persistence exports
The SDK entrypoint re-exports the OPFS/session persistence helpers used by the
browser panel so external callers can stage the same files without importing
private module paths:
- `getOpfsRoot()`, `saveTextFile()`, and `loadTextFile()` for browser OPFS text
persistence.
- `machineIniPath()`, `toolTablePath()`, `parameterFilePath()`,
`gcodeProgramPath()`, `sessionSnapshotPath()`, `defaultMachinePaths()`, and
`normalizeOpfsPath()` for stable OPFS path construction.
- `createMachineSessionSnapshotPayload()`, `createSessionSnapshot()`,
`saveSessionSnapshot()`, `saveMachineSessionSnapshot()`,
`loadMachineSessionSnapshot()`, and `validateSessionSnapshot()` for session
snapshot persistence.
- `machineFilePaths()`, `saveMachineTextFiles()`, `loadMachineTextFiles()`, and
`gcodeFilenameFromProgramPath()` for machine-file and G-code file helpers.
- `readMachineSessionReadiness()` for checking persisted INI, parameter,
tool-table, optional G-code, and optional session snapshot files before
loading a session.
- `restoreMachineParametersFromOpfs()`, `loadMachineToolTableFromOpfs()`, and
`loadMachineSessionFromOpfs()` for loading OPFS machine state into the
LinuxCNC-backed interpreter SDK.
These helpers only move and validate host files. Parameter and tool-table
effects still come from the LinuxCNC-backed interpreter SDK methods they call.
## INI panel shell handoff exports
The SDK entrypoint also re-exports the stable INI panel shell handoff surface
for external browser shells:
- `INI_PANEL_ENTRIES`, `getIniPanelEntryManifest()`,
`getIniPanelEntryByHref()`, `getVisibleIniPanelEntries()`, and
`createIniPanelLauncherLinkViewModel()` for discovering launch pages and
rendering shell-owned launcher links from the stable entry manifest.
- `createIniPanelLaunchApiManifest()` and
`isSupportedIniPanelLaunchApiManifest()` for launch API negotiation.
- `createControlPageBrowserApiSchema()`,
`createControlPageBrowserApiManifest()`,
`createControlPageBrowserApiMethods()`,
`createIniPanelShellControlPageContract()`, and
`isSupportedIniPanelControlPageApiManifest()` for read-only control-page API
negotiation.
- `createIniPanelShellIntegrationSchema()`,
`createIniPanelShellIntegrationManifest()`,
`createIniPanelShellIntegrationReadiness()`,
`createIniPanelShellIntegrationWorkflowPlan()`, and
`isSupportedIniPanelShellIntegrationManifest()` for checking launch and
control-page compatibility before embedding.
- `createIniPanelShellViewModel()` for a complete read-only shell integration
bundle.
- `createMachineSessionPersistenceSummary()` for a machine-readable OPFS/session
persistence summary covering machine files, session snapshot, session
readiness, session load, readonly status, and shell handoff readiness rows.
`createMachineSessionPersistenceDisplayViewModel()` and
`createMachineSessionPersistenceRenderState()` turn the summary into stable
display and render data.
`createIniPanelShellSessionHandoffWorkflowSummary()` can include this as the
read-only `persistence-summary` row.
- `createIniPanelShellWorkflowOverviewContract()` for discovering the workflow
overview iframe API contract.
- `validateIniPanelShellWorkflowOverviewReleaseReadinessArtifactJson()` for the
read-only workflow overview artifact validation wrapper exposed by
`workflow-overview.html`. Use
`runIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflow()` when
a shell already has artifact JSON and needs parse, validation, summary,
action-plan, and gate execution summary output in one non-throwing workflow.
`createIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflowSummaryViewModel()`
turns that JSON workflow into stable dashboard status text and rows.
`createIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflowActionPlan()`
exposes the JSON workflow next-command plan without requiring callers to read
internal workflow fields.
`createIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflowDomContract()`,
`createIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflowDomReadiness()`,
`renderIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflowState()`,
and `mountIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflowState()`
let a shell render or mount that JSON workflow render-state into caller-owned
DOM without executing release gate commands.
Use
`loadIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrl()` when a
browser or shell should fetch the artifact JSON URL, validate it, and
optionally mount the resulting render-state into caller-owned DOM. The URL
workflow result includes `validationSummaryViewModel` and
`gateExecutionSummaryViewModel` for dashboards.
`createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowSummaryViewModel()`
turns the full URL workflow result into stable status text and rows.
`createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowActionPlan()`
exposes the same URL workflow next-action/next-command plan through the
browser workflow overview surface.
`createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowRenderState()`,
`createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowDomContract()`,
`createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowDomReadiness()`,
`renderIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowState()`,
and `mountIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowState()`
let callers render the URL workflow status into their own DOM.
`createIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationActionPlan()`
exposes the direct artifact-validation next-command plan when callers already
have parsed or validated artifact JSON.
`createIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationActionPlanRenderState()`,
`createIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationActionPlanDomContract()`,
`createIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationActionPlanDomReadiness()`,
`renderIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationActionPlanState()`,
and `mountIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationActionPlanState()`
let callers render that validation action plan into caller-owned DOM.
`createIniPanelShellWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryViewModel()`
and
`createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlGateExecutionSummaryViewModel()`
expose the embedded release gate execution summary for external shells without
requiring callers to parse artifact internals.
`createIniPanelShellWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryRenderState()`,
`createIniPanelShellWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryDomContract()`,
`createIniPanelShellWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryDomReadiness()`,
`renderIniPanelShellWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryState()`,
and `mountIniPanelShellWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryState()`
let callers render the per-gate execution summary rows into caller-owned DOM.
`createIniPanelShellWorkflowOverviewReleaseReadinessArtifactDisplayViewModel()`,
`createIniPanelShellWorkflowOverviewReleaseReadinessArtifactRenderState()`,
`createIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationSummaryViewModel()`,
`createIniPanelShellWorkflowOverviewReleaseReadinessArtifactDomContract()`,
`createIniPanelShellWorkflowOverviewReleaseReadinessArtifactDomReadiness()`,
`renderIniPanelShellWorkflowOverviewReleaseReadinessArtifactState()`, and
`mountIniPanelShellWorkflowOverviewReleaseReadinessArtifactState()` when an
outer shell needs to render that validation into caller-owned DOM.
- `createIniPanelShellApiSurfaceInventory()` for a machine-readable inventory
of launch, control-page, workflow overview, persistence, and session
readiness report API counts, including persistence summary row counts.
`createIniPanelShellViewModel()` also includes
this inventory as a read-only workflow summary row so browser overview pages
and external shells can display API coverage without private imports.
- `createIniPanelShellSessionReadinessWorkflowReport()` for a machine-readable
session readiness workflow report with phase, missing reasons, status line,
and stable rows.
- `createIniPanelShellWorkflowOverviewEmbeddingMountDomContract()` and
`createIniPanelShellWorkflowOverviewEmbeddingMountDomReadiness()` for
workflow overview embedding mount DOM checks.
- `renderIniPanelShellWorkflowOverviewEmbeddingMountState()` for rendering a
mount render-state into caller-owned DOM.
- `mountIniPanelShellWorkflowOverviewEmbeddingMountState()` for a non-throwing
workflow overview embedding mount wrapper that returns structured blocked
results when DOM nodes are missing.
The workflow overview embedding helpers are UI/runtime boundary helpers only.
They render status and rows already produced by the shell view-model; they do
not parse G-code, execute action plans, or implement CNC semantics.

View File

@@ -1,7 +1,178 @@
export { createLinuxCncIniSdk } from "./linuxcnc-ini.js";
export { createLinuxCncInterpSdk } from "./linuxcnc-interp.js";
export {
createProjectBatchAcceptanceActionPlan,
createProjectBatchAcceptanceCapabilityMatrix,
createProjectBatchAcceptanceChecklist,
createProjectBatchAcceptanceReport,
createProjectBatchAcceptanceReportJsonWorkflow,
createProjectBatchAcceptanceReportUrlWorkflowActionPlan,
createProjectBatchAcceptanceReportUrlWorkflowSummaryViewModel,
createProjectBatchAcceptanceReportValidationActionPlan,
createProjectBatchAcceptanceReportValidationSummaryViewModel,
createProjectBatchAcceptanceReportValidation,
createProjectBatchAcceptanceSummaryViewModel,
createProjectBatchAcceptanceWorkflow,
createProjectReleaseGateActionPlan,
createProjectReleaseGateExecutionManifest,
createProjectReleaseGateExecutionSummaryViewModel,
createProjectReleaseGateManifest,
createProjectReleaseGateResultMatrix,
createProjectReleaseReadinessArtifactJsonWorkflow,
createProjectReleaseReadinessArtifactUrlWorkflowActionPlan,
createProjectReleaseReadinessArtifactUrlWorkflowSummaryViewModel,
createProjectReleaseReadinessArtifactValidation,
createProjectReleaseReadinessArtifactValidationActionPlan,
createProjectReleaseReadinessArtifactValidationSummaryViewModel,
createProjectReleaseReadinessReport,
createProjectReleaseReadinessSummaryViewModel,
loadProjectBatchAcceptanceReportUrlWorkflow,
loadProjectReleaseReadinessArtifactUrlWorkflow,
parseProjectReleaseReadinessArtifactJson,
} from "./project-release-readiness.js";
export {
analyzeIniRuntimeBoundaries,
planIniFileContextStaging,
planSimConfigStaging,
} from "./sim-config-staging.js";
export {
defaultMachinePaths,
gcodeProgramPath,
machineIniPath,
machineRoot,
normalizeOpfsPath,
opfsPath,
parameterFilePath,
previewCachePath,
sessionSnapshotPath,
splitOpfsPath,
toolTablePath,
} from "../../opfs/path-model.js";
export {
ensureParentDir,
getOpfsRoot,
loadTextFile,
saveTextFile,
} from "../../opfs/file-service.js";
export {
createMachineSessionSnapshotPayload,
createSessionSnapshot,
loadMachineSessionSnapshot,
loadSessionSnapshot,
saveMachineSessionSnapshot,
saveSessionSnapshot,
validateSessionSnapshot,
} from "../../opfs/snapshot-store.js";
export {
gcodeFilenameFromProgramPath,
loadGcodeProgram,
loadMachineTextFiles,
machineFilePaths,
saveGcodeProgram,
saveMachineTextFiles,
} from "../../opfs/machine-file-store.js";
export {
restoreMachineParametersFromOpfs,
saveMachineParametersToOpfs,
} from "../../opfs/linuxcnc-parameter-bridge.js";
export {
loadMachineToolTableFromOpfs,
saveMachineToolTableToOpfs,
} from "../../opfs/linuxcnc-tool-table-bridge.js";
export { loadMachineSessionFromOpfs } from "../../opfs/linuxcnc-machine-session-bridge.js";
export { readMachineSessionReadiness } from "../../opfs/machine-session-readiness.js";
export {
INI_PANEL_ENTRIES,
createControlPageBrowserApiManifest,
createControlPageBrowserApiMethods,
createControlPageBrowserApiSchema,
createIniPanelLaunchApiManifest,
createIniPanelLaunchApiSchema,
createIniPanelLauncherLinkViewModel,
createMachineSessionPersistenceDisplayViewModel,
createMachineSessionPersistenceRenderState,
createMachineSessionPersistenceSummary,
createIniPanelShellIntegrationManifest,
createIniPanelShellIntegrationReadiness,
createIniPanelShellIntegrationSchema,
createIniPanelShellIntegrationWorkflowPlan,
createIniPanelShellControlPageContract,
createIniPanelShellSessionHandoffCompatibilityReport,
createIniPanelShellSessionHandoffPackage,
createIniPanelShellSessionHandoffPackageMountDisplayViewModel,
createIniPanelShellSessionHandoffPackageMountRenderState,
createIniPanelShellSessionHandoffPackageMountState,
createIniPanelShellSessionHandoffPackageReport,
createIniPanelShellSessionHandoffPackageRenderState,
createIniPanelShellSessionHandoffPackageSchema,
createIniPanelShellSessionReadinessWorkflowReport,
createIniPanelShellApiSurfaceInventory,
createIniPanelShellSessionHandoffSummary,
createIniPanelShellSessionHandoffSummarySchema,
createIniPanelShellViewModel,
createIniPanelShellWorkflowOverviewContract,
createIniPanelShellWorkflowOverviewEmbeddingDisplayViewModel,
createIniPanelShellWorkflowOverviewEmbeddingDomContract,
createIniPanelShellWorkflowOverviewEmbeddingDomReadiness,
createIniPanelShellWorkflowOverviewEmbeddingMountDisplayViewModel,
createIniPanelShellWorkflowOverviewEmbeddingMountDomContract,
createIniPanelShellWorkflowOverviewEmbeddingMountDomReadiness,
createIniPanelShellWorkflowOverviewEmbeddingMountRenderState,
createIniPanelShellWorkflowOverviewEmbeddingRenderState,
createIniPanelShellWorkflowOverviewEmbeddingReport,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryViewModel,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlGateExecutionSummaryViewModel,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryRenderState,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryDomContract,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryDomReadiness,
renderIniPanelShellWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryState,
mountIniPanelShellWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryState,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationSummaryViewModel,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowActionPlan,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowSummaryViewModel,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowRenderState,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowDomContract,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowDomReadiness,
renderIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowState,
mountIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowState,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationActionPlan,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationActionPlanRenderState,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationActionPlanDomContract,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationActionPlanDomReadiness,
renderIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationActionPlanState,
mountIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationActionPlanState,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactDisplayViewModel,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactDomContract,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactDomReadiness,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactRenderState,
createIniPanelShellWorkflowOverviewReadiness,
getIniPanelEntryByHref,
getIniPanelEntryManifest,
getVisibleIniPanelEntries,
isSupportedIniPanelControlPageApiManifest,
isSupportedIniPanelLaunchApiManifest,
isSupportedIniPanelShellIntegrationManifest,
isSupportedIniPanelShellSessionHandoffPackage,
isSupportedIniPanelShellSessionHandoffSummary,
loadIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrl,
runIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflow,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflowSummaryViewModel,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflowActionPlan,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflowRenderState,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflowDomContract,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflowDomReadiness,
renderIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflowState,
mountIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflowState,
mountIniPanelShellSessionHandoff,
mountIniPanelShellWorkflowOverviewEmbedding,
mountIniPanelShellWorkflowOverviewEmbeddingMountState,
mountIniPanelShellWorkflowOverviewEmbeddingState,
renderIniPanelShellSessionHandoffMountState,
renderIniPanelShellWorkflowOverviewReleaseReadinessArtifactState,
renderIniPanelShellWorkflowOverviewEmbeddingMountState,
renderIniPanelShellWorkflowOverviewEmbeddingState,
mountIniPanelShellWorkflowOverviewReleaseReadinessArtifactState,
validateIniPanelShellWorkflowOverviewReleaseReadinessArtifactJson,
} from "../../ui/ini-panel/ui-shell.js";

File diff suppressed because it is too large Load Diff

View File

@@ -18,6 +18,9 @@ import {
import {
loadMachineSessionFromOpfs,
} from "../../opfs/linuxcnc-machine-session-bridge.js";
import {
readMachineSessionReadiness,
} from "../../opfs/machine-session-readiness.js";
import {
loadMachineSessionSnapshot,
saveMachineSessionSnapshot,
@@ -37,11 +40,15 @@ import {
} from "./run-workflow.js";
import {
loadMachineSessionIntoWasmWorkflow,
readMachineSessionReadinessWorkflow,
restoreMachineSessionSnapshotWorkflow,
saveMachineSessionSnapshotWorkflow,
} from "./session-workflow.js";
import {
createMachineSessionControlView,
createMachineSessionPersistenceDisplayViewModel,
createMachineSessionPersistenceRenderState,
createMachineSessionPersistenceSummary,
createMachineSessionStateSnapshot,
createMachineSessionStateBundle,
createMachineSessionStateReport,
@@ -167,6 +174,25 @@ function getMachineSessionWorkflowStatus() {
return createMachineSessionWorkflowStatus(window.linuxCncIniPanelMachineSessionState);
}
function getMachineSessionPersistenceSummary(options = {}) {
return createMachineSessionPersistenceSummary({
state: window.linuxCncIniPanelMachineSessionState,
...options,
});
}
function getMachineSessionPersistenceDisplayViewModel(options = {}) {
return createMachineSessionPersistenceDisplayViewModel(
getMachineSessionPersistenceSummary(options),
);
}
function getMachineSessionPersistenceRenderState(options = {}) {
return createMachineSessionPersistenceRenderState(
getMachineSessionPersistenceDisplayViewModel(options),
);
}
function getMachineSessionStateBundle() {
return createMachineSessionStateBundle(window.linuxCncIniPanelMachineSessionState);
}
@@ -199,6 +225,9 @@ window.linuxCncIniPanelApi = {
getMachineSessionStateReport,
getMachineSessionStateReportExport,
getMachineSessionWorkflowStatus,
getMachineSessionPersistenceSummary,
getMachineSessionPersistenceDisplayViewModel,
getMachineSessionPersistenceRenderState,
renderMachineSessionControlView: renderCurrentMachineSessionControlView,
};
window.getMachineSessionControlView = getMachineSessionControlView;
@@ -206,6 +235,9 @@ window.getMachineSessionStateBundle = getMachineSessionStateBundle;
window.getMachineSessionStateReport = getMachineSessionStateReport;
window.getMachineSessionStateReportExport = getMachineSessionStateReportExport;
window.getMachineSessionWorkflowStatus = getMachineSessionWorkflowStatus;
window.getMachineSessionPersistenceSummary = getMachineSessionPersistenceSummary;
window.getMachineSessionPersistenceDisplayViewModel = getMachineSessionPersistenceDisplayViewModel;
window.getMachineSessionPersistenceRenderState = getMachineSessionPersistenceRenderState;
window.renderMachineSessionControlView = renderCurrentMachineSessionControlView;
window.linuxCncIniPanelMachineSessionState = panelMachineSessionState;
renderCurrentMachineSessionControlView();
@@ -486,6 +518,14 @@ async function loadMachineSessionIntoWasm() {
return loadedSession;
}
function readCurrentMachineSessionReadiness() {
return readMachineSessionReadiness(MACHINE_ID, {
sessionId: UI_SESSION.snapshotId,
snapshotFilename: UI_SESSION.snapshotFilename,
gcodeFilename: UI_SESSION.defaultGcodeFilename,
});
}
async function loadSample() {
setLog(`Fetching ${SAMPLE_PATH}`);
const response = await fetch(SAMPLE_PATH);
@@ -674,6 +714,7 @@ document.getElementById("restore-session-snapshot").addEventListener("click", as
snapshotLabel: sessionSnapshotLabel(UI_SESSION),
loadMachineSessionSnapshot,
loadMachineTextFiles,
readReadiness: readCurrentMachineSessionReadiness,
});
editor.value = workflow.editorIniText;
setField("sessionIni", workflow.fields.sessionIni);
@@ -684,6 +725,7 @@ document.getElementById("restore-session-snapshot").addEventListener("click", as
restoredSessionSnapshot = workflow.snapshot;
updatePanelMachineSessionState({
sessionSnapshot: workflow.summary,
sessionReadiness: workflow.readiness,
sessionLoad: null,
selectedProgram: null,
fields: workflow.fields,
@@ -707,17 +749,27 @@ document.getElementById("restore-session-snapshot").addEventListener("click", as
document.getElementById("load-session").addEventListener("click", async () => {
try {
const readinessWorkflow = await readMachineSessionReadinessWorkflow({
readReadiness: readCurrentMachineSessionReadiness,
});
const workflow = await loadMachineSessionIntoWasmWorkflow({
loadSession: () => loadMachineSessionIntoWasm(),
});
loadedSession = workflow.loadedSession;
updatePanelMachineSessionState({
sessionReadiness: readinessWorkflow.readiness,
sessionLoad: workflow.loadSummary,
fields: workflow.fields,
fields: {
...readinessWorkflow.fields,
...workflow.fields,
},
lastAction: "load-session",
error: null,
});
setLog(workflow.logLines.join("\n"));
setLog([
...readinessWorkflow.logLines,
...workflow.logLines,
].join("\n"));
} catch (error) {
updatePanelMachineSessionState({
lastAction: "load-session",
@@ -736,6 +788,7 @@ document.getElementById("restore-load-session").addEventListener("click", async
snapshotLabel: sessionSnapshotLabel(UI_SESSION),
loadMachineSessionSnapshot,
loadMachineTextFiles,
readReadiness: readCurrentMachineSessionReadiness,
loadSession: () => loadMachineSessionIntoWasm(),
});
editor.value = workflow.editorIniText;
@@ -750,6 +803,7 @@ document.getElementById("restore-load-session").addEventListener("click", async
}
updatePanelMachineSessionState({
sessionSnapshot: workflow.summary,
sessionReadiness: workflow.readiness,
sessionLoad: workflow.loadSummary,
selectedProgram: null,
fields: workflow.fields,

View File

@@ -5,6 +5,7 @@ export function createControlPageSessionLoadState({
controlViewReady = false,
stateBundle = null,
workflowStatus = null,
sessionReadiness = null,
controlView = null,
error = null,
} = {}) {
@@ -15,11 +16,19 @@ export function createControlPageSessionLoadState({
controlViewReady,
stateBundle,
workflowStatus,
sessionReadiness,
controlView,
error,
};
}
function getSessionReadiness(stateBundle, workflowStatus) {
return workflowStatus?.sessionReadiness
?? stateBundle?.workflowStatus?.sessionReadiness
?? stateBundle?.report?.sessionReadiness
?? null;
}
export function loadMachineSessionForControlPageWorkflow({
getPanelWindow,
getPanelApi,
@@ -63,22 +72,28 @@ export function loadMachineSessionForControlPageWorkflow({
}
if (!controlView) {
const stateBundle = panelApi.getMachineSessionStateBundle?.() ?? null;
const workflowStatus = panelApi.getMachineSessionWorkflowStatus?.() ?? null;
return createControlPageSessionLoadState({
phase: "waiting-session",
panelReady: true,
apiReady: true,
stateBundle: panelApi.getMachineSessionStateBundle?.() ?? null,
workflowStatus: panelApi.getMachineSessionWorkflowStatus?.() ?? null,
stateBundle,
workflowStatus,
sessionReadiness: getSessionReadiness(stateBundle, workflowStatus),
});
}
const stateBundle = panelApi.getMachineSessionStateBundle?.() ?? null;
const workflowStatus = panelApi.getMachineSessionWorkflowStatus?.() ?? null;
return createControlPageSessionLoadState({
phase: "ready",
panelReady: true,
apiReady: true,
controlViewReady: true,
stateBundle: panelApi.getMachineSessionStateBundle?.() ?? null,
workflowStatus: panelApi.getMachineSessionWorkflowStatus?.() ?? null,
stateBundle,
workflowStatus,
sessionReadiness: getSessionReadiness(stateBundle, workflowStatus),
controlView,
});
}

View File

@@ -5,8 +5,11 @@ import {
createMachineSessionControlPageController,
} from "./control-page-controller.js";
import {
createControlPageBrowserApiManifest,
createControlPageBrowserApiMethods,
createControlPageBrowserApiSchema,
createIniPanelShellControlPageContract,
isSupportedIniPanelControlPageApiManifest,
} from "./ui-shell.js";
const panelFrameNode = document.getElementById("panel-frame");
@@ -23,6 +26,9 @@ const controller = createMachineSessionControlPageController({
controller.mount();
window.linuxCncIniPanelControlPageApi = {
isSupportedControlPageApiManifest: isSupportedIniPanelControlPageApiManifest,
getControlPageApiSchema: createControlPageBrowserApiSchema,
getControlPageApiManifest: createControlPageBrowserApiManifest,
getControlPageApiMethods: createControlPageBrowserApiMethods,
getControlView: controller.getControlView,
getControlPageContract: createIniPanelShellControlPageContract,

View File

@@ -9,6 +9,11 @@ export const INI_PANEL_ENTRIES = [
title: "Open read-only control view",
href: "./control-page.html",
},
{
id: "workflow-overview",
title: "Open shell handoff overview",
href: "./workflow-overview.html",
},
];
export function getIniPanelEntryManifest() {

View File

@@ -46,6 +46,27 @@
display: grid;
gap: 8px;
}
.handoff-status {
border-top: 1px solid var(--border);
padding-top: 12px;
display: grid;
gap: 8px;
}
.handoff-status dl {
margin: 0;
display: grid;
grid-template-columns: minmax(120px, 180px) 1fr;
gap: 6px 12px;
font: 13px/1.4 "Segoe UI", "Noto Sans", sans-serif;
}
.handoff-status dt {
color: var(--muted);
}
.handoff-status dd {
margin: 0;
color: var(--text);
overflow-wrap: anywhere;
}
a {
color: var(--accent);
text-decoration: none;
@@ -65,11 +86,25 @@
<main>
<section class="panel">
<h1>LinuxCNC INI Panel</h1>
<p>Choose the editable panel or the read-only control view.</p>
<p>Choose the editable panel, the read-only control view, or the shell handoff overview.</p>
<div class="links">
<a data-entry-id="edit-run" href="./index.html">Open edit and run panel</a>
<a data-entry-id="control-view" href="./control-page.html">Open read-only control view</a>
<a data-entry-id="workflow-overview" href="./workflow-overview.html">Open shell handoff overview</a>
<a data-entry-id="real-simulation" href="../simulation/index.html">Open real browser simulation</a>
</div>
<section class="handoff-status" aria-label="Shell handoff compatibility" data-handoff-shell>
<p data-handoff-status>Checking shell handoff compatibility.</p>
<dl data-handoff-rows></dl>
</section>
<section class="handoff-status" aria-label="Shell handoff mount readiness" data-handoff-mount>
<p data-handoff-mount-status>Checking shell handoff mount readiness.</p>
<dl data-handoff-mount-rows></dl>
</section>
<section class="handoff-status" aria-label="Shell handoff workflow summary" data-handoff-workflow>
<p data-handoff-workflow-status>Checking shell handoff workflow summary.</p>
<dl data-handoff-workflow-rows></dl>
</section>
</section>
</main>
<script type="module">
@@ -77,6 +112,21 @@
createIniPanelLauncherLinkViewModel,
createIniPanelShellViewModel,
isSupportedIniPanelLaunchApiManifest,
createIniPanelShellSessionHandoffMountDomContract,
createIniPanelShellSessionHandoffMountDomReadiness,
mountIniPanelShellSessionHandoff,
mountIniPanelShellSessionHandoffFromControlPage,
readIniPanelShellSessionHandoffStatusFromControlPage,
createMachineSessionPersistenceSummary,
createIniPanelShellSessionHandoffWorkflowSummary,
createIniPanelShellSessionReadinessWorkflowReport,
createIniPanelShellSessionHandoffWorkflowDisplayViewModel,
createIniPanelShellSessionHandoffWorkflowRenderState,
createIniPanelShellSessionHandoffWorkflowDomContract,
createIniPanelShellSessionHandoffWorkflowDomReadiness,
renderIniPanelShellSessionHandoffWorkflowState,
mountIniPanelShellSessionHandoffWorkflow,
renderIniPanelShellSessionHandoffMountState,
} from "./ui-shell.js";
const shellViewModel = createIniPanelShellViewModel();
@@ -89,11 +139,327 @@
link.textContent = linkModel.label;
link.href = linkModel.href;
}
const handoffRenderState = shellViewModel.shellSessionHandoffPackageRenderState;
const handoffShell = document.querySelector("[data-handoff-shell]");
handoffShell.dataset.handoffPhase = handoffRenderState.dataset.handoffPhase;
handoffShell.dataset.handoffReady = handoffRenderState.dataset.handoffReady;
handoffShell.dataset.handoffScope = handoffRenderState.dataset.handoffScope;
document.querySelector("[data-handoff-status]").textContent = handoffRenderState.statusLine;
const handoffRows = document.querySelector("[data-handoff-rows]");
for (const row of handoffRenderState.rows) {
const term = document.createElement("dt");
term.dataset.handoffRow = row.id;
term.textContent = row.label;
const detail = document.createElement("dd");
detail.dataset.handoffValue = row.id;
detail.textContent = row.value;
handoffRows.append(term, detail);
}
const handoffMountRenderState = shellViewModel.shellSessionHandoffPackageMountRenderState;
renderIniPanelShellSessionHandoffMountState(handoffMountRenderState, { documentRef: document });
const handoffWorkflowRenderState = shellViewModel.shellSessionHandoffWorkflowRenderState;
renderIniPanelShellSessionHandoffWorkflowState(handoffWorkflowRenderState, { documentRef: document });
window.linuxCncIniPanelLaunchApi = {
isSupportedLaunchApiManifest: isSupportedIniPanelLaunchApiManifest,
getLaunchApiSchema: () => shellViewModel.launchApiSchema,
getLaunchApiManifest: () => shellViewModel.launchApiManifest,
getShellIntegrationSchema: () => shellViewModel.shellIntegrationSchema,
getShellIntegrationManifest: () => shellViewModel.shellIntegrationManifest,
getShellIntegrationReadiness: () => shellViewModel.shellIntegrationReadiness,
getShellSessionHandoffSummarySchema: () => shellViewModel.shellSessionHandoffSummarySchema,
getShellSessionHandoffSummary: () => shellViewModel.shellSessionHandoffSummary,
isSupportedShellSessionHandoffSummary: shellViewModel.isSupportedShellSessionHandoffSummary,
getShellSessionHandoffCompatibilityReport: () => shellViewModel.shellSessionHandoffCompatibilityReport,
getShellSessionHandoffDisplayViewModel: () => shellViewModel.shellSessionHandoffDisplayViewModel,
getShellSessionHandoffSnapshot: () => shellViewModel.shellSessionHandoffSnapshot,
getShellSessionHandoffRenderState: () => shellViewModel.shellSessionHandoffRenderState,
getShellSessionHandoffActionPlan: () => shellViewModel.shellSessionHandoffActionPlan,
getShellSessionHandoffChecklist: () => shellViewModel.shellSessionHandoffChecklist,
getShellSessionHandoffPackageSchema: () => shellViewModel.shellSessionHandoffPackageSchema,
getShellSessionHandoffPackage: () => shellViewModel.shellSessionHandoffPackage,
getShellSessionHandoffPackageReport: () => shellViewModel.shellSessionHandoffPackageReport,
getShellSessionHandoffPackageDisplayViewModel: () => shellViewModel.shellSessionHandoffPackageDisplayViewModel,
getShellSessionHandoffPackageRenderState: () => shellViewModel.shellSessionHandoffPackageRenderState,
getShellSessionHandoffPackageMountState: (sessionLoadState) => (
sessionLoadState
? shellViewModel.createShellSessionHandoffPackageMountState({
handoffPackage: shellViewModel.shellSessionHandoffPackage,
packageRenderState: shellViewModel.shellSessionHandoffPackageRenderState,
sessionLoadState,
})
: shellViewModel.shellSessionHandoffPackageMountState
),
getShellSessionHandoffPackageMountDisplayViewModel: (mountState) => (
shellViewModel.createShellSessionHandoffPackageMountDisplayViewModel(
mountState ?? shellViewModel.shellSessionHandoffPackageMountState,
)
),
getShellSessionHandoffPackageMountRenderState: (mountState) => (
shellViewModel.createShellSessionHandoffPackageMountRenderState(
shellViewModel.createShellSessionHandoffPackageMountDisplayViewModel(
mountState ?? shellViewModel.shellSessionHandoffPackageMountState,
),
)
),
getShellSessionHandoffMountDomContract: (options) => (
createIniPanelShellSessionHandoffMountDomContract(options)
),
getShellSessionHandoffMountDomReadiness: (options) => (
createIniPanelShellSessionHandoffMountDomReadiness(options ?? { documentRef: document })
),
renderShellSessionHandoffMountState: (renderState, options) => (
renderIniPanelShellSessionHandoffMountState(renderState, options ?? { documentRef: document })
),
mountShellSessionHandoff: (options) => (
mountIniPanelShellSessionHandoff(options ?? {
renderState: shellViewModel.shellSessionHandoffPackageMountRenderState,
documentRef: document,
})
),
mountShellSessionHandoffFromControlPage: (options) => (
mountIniPanelShellSessionHandoffFromControlPage({
handoffPackage: shellViewModel.shellSessionHandoffPackage,
packageRenderState: shellViewModel.shellSessionHandoffPackageRenderState,
documentRef: document,
...(options ?? {}),
})
),
readShellSessionHandoffStatusFromControlPage: (options) => (
readIniPanelShellSessionHandoffStatusFromControlPage({
handoffSummary: shellViewModel.shellSessionHandoffSummary,
...(options ?? {}),
})
),
getMachineSessionPersistenceSummary: (options) => (
createMachineSessionPersistenceSummary(options)
),
getShellSessionHandoffWorkflowSummary: (options) => (
createIniPanelShellSessionHandoffWorkflowSummary(options ?? {
mountWorkflow: {
phase: shellViewModel.shellSessionHandoffPackageMountRenderState.phase,
ready: shellViewModel.shellSessionHandoffPackageMountRenderState.ready,
statusLine: shellViewModel.shellSessionHandoffPackageMountRenderState.statusLine,
},
controlPageMountWorkflow: {
phase: shellViewModel.shellSessionHandoffPackageMountState.phase,
ready: shellViewModel.shellSessionHandoffPackageMountState.ready,
statusLine: shellViewModel.shellSessionHandoffPackageMountState.statusLine,
},
readonlyStatusResult: shellViewModel.readShellSessionHandoffStatusFromControlPage({
handoffSummary: shellViewModel.shellSessionHandoffSummary,
}),
})
),
getShellSessionReadinessWorkflowReport: (options) => (
createIniPanelShellSessionReadinessWorkflowReport(options ?? {
workflowSummary: shellViewModel.shellSessionHandoffWorkflowSummary,
})
),
getShellApiSurfaceInventory: (options) => (
shellViewModel.createShellApiSurfaceInventory(options ?? {
launchApiManifest: shellViewModel.launchApiManifest,
controlPageApiManifest: shellViewModel.controlPage.browserApiManifest,
workflowOverviewContract: shellViewModel.shellWorkflowOverviewContract,
sessionReadinessWorkflowReport: shellViewModel.shellSessionReadinessWorkflowReport,
})
),
getShellSessionHandoffWorkflowDisplayViewModel: (options) => (
createIniPanelShellSessionHandoffWorkflowDisplayViewModel(
options ?? shellViewModel.shellSessionHandoffWorkflowSummary,
)
),
getShellSessionHandoffWorkflowRenderState: (options) => (
createIniPanelShellSessionHandoffWorkflowRenderState(
options ?? shellViewModel.shellSessionHandoffWorkflowDisplayViewModel,
)
),
getShellSessionHandoffWorkflowDomContract: (options) => (
createIniPanelShellSessionHandoffWorkflowDomContract(options)
),
getShellSessionHandoffWorkflowDomReadiness: (options) => (
createIniPanelShellSessionHandoffWorkflowDomReadiness(options ?? { documentRef: document })
),
renderShellSessionHandoffWorkflowState: (renderState, options) => (
renderIniPanelShellSessionHandoffWorkflowState(renderState, options ?? { documentRef: document })
),
mountShellSessionHandoffWorkflow: (options) => (
mountIniPanelShellSessionHandoffWorkflow(options ?? {
renderState: shellViewModel.shellSessionHandoffWorkflowRenderState,
documentRef: document,
})
),
getWorkflowOverviewContract: (options) => (
shellViewModel.createWorkflowOverviewContract(options)
),
getWorkflowOverviewReadiness: (options) => (
shellViewModel.createWorkflowOverviewReadiness(options)
),
mountWorkflowOverviewEmbedding: (options) => (
shellViewModel.mountWorkflowOverviewEmbedding(options)
),
runWorkflowOverviewReleaseReadinessArtifactJsonWorkflow: (options) => (
shellViewModel.runWorkflowOverviewReleaseReadinessArtifactJsonWorkflow(options)
),
getWorkflowOverviewReleaseReadinessArtifactJsonWorkflowSummaryViewModel: (workflow) => (
shellViewModel.createWorkflowOverviewReleaseReadinessArtifactJsonWorkflowSummaryViewModel(workflow)
),
getWorkflowOverviewReleaseReadinessArtifactJsonWorkflowActionPlan: (workflow) => (
shellViewModel.createWorkflowOverviewReleaseReadinessArtifactJsonWorkflowActionPlan(workflow)
),
getWorkflowOverviewReleaseReadinessArtifactJsonWorkflowRenderState: (summaryViewModel) => (
shellViewModel.createWorkflowOverviewReleaseReadinessArtifactJsonWorkflowRenderState(summaryViewModel)
),
getWorkflowOverviewReleaseReadinessArtifactJsonWorkflowDomContract: (options) => (
shellViewModel.createWorkflowOverviewReleaseReadinessArtifactJsonWorkflowDomContract(options)
),
getWorkflowOverviewReleaseReadinessArtifactJsonWorkflowDomReadiness: (options) => (
shellViewModel.createWorkflowOverviewReleaseReadinessArtifactJsonWorkflowDomReadiness(
options ?? { documentRef: document },
)
),
renderWorkflowOverviewReleaseReadinessArtifactJsonWorkflowState: (renderState, options) => (
shellViewModel.renderWorkflowOverviewReleaseReadinessArtifactJsonWorkflowState(
renderState,
options ?? { documentRef: document },
)
),
mountWorkflowOverviewReleaseReadinessArtifactJsonWorkflowState: (options) => (
shellViewModel.mountWorkflowOverviewReleaseReadinessArtifactJsonWorkflowState(
options ?? { documentRef: document },
)
),
loadWorkflowOverviewReleaseReadinessArtifactUrl: (options) => (
shellViewModel.loadWorkflowOverviewReleaseReadinessArtifactUrl(options)
),
getWorkflowOverviewReleaseReadinessArtifactUrlWorkflowSummaryViewModel: (workflow) => (
shellViewModel.createWorkflowOverviewReleaseReadinessArtifactUrlWorkflowSummaryViewModel(workflow)
),
getWorkflowOverviewReleaseReadinessArtifactUrlWorkflowActionPlan: (workflow) => (
shellViewModel.createWorkflowOverviewReleaseReadinessArtifactUrlWorkflowActionPlan(workflow)
),
getWorkflowOverviewReleaseReadinessArtifactUrlWorkflowRenderState: (summaryViewModel) => (
shellViewModel.createWorkflowOverviewReleaseReadinessArtifactUrlWorkflowRenderState(summaryViewModel)
),
getWorkflowOverviewReleaseReadinessArtifactUrlWorkflowDomContract: (options) => (
shellViewModel.createWorkflowOverviewReleaseReadinessArtifactUrlWorkflowDomContract(options)
),
getWorkflowOverviewReleaseReadinessArtifactUrlWorkflowDomReadiness: (options) => (
shellViewModel.createWorkflowOverviewReleaseReadinessArtifactUrlWorkflowDomReadiness(
options ?? { documentRef: document },
)
),
renderWorkflowOverviewReleaseReadinessArtifactUrlWorkflowState: (renderState, options) => (
shellViewModel.renderWorkflowOverviewReleaseReadinessArtifactUrlWorkflowState(
renderState,
options ?? { documentRef: document },
)
),
mountWorkflowOverviewReleaseReadinessArtifactUrlWorkflowState: (options) => (
shellViewModel.mountWorkflowOverviewReleaseReadinessArtifactUrlWorkflowState(
options ?? { documentRef: document },
)
),
getWorkflowOverviewReleaseReadinessArtifactValidationSummaryViewModel: (validation) => (
shellViewModel.createWorkflowOverviewReleaseReadinessArtifactValidationSummaryViewModel(validation)
),
getWorkflowOverviewReleaseReadinessArtifactValidationActionPlan: (validation) => (
shellViewModel.createWorkflowOverviewReleaseReadinessArtifactValidationActionPlan(validation)
),
getWorkflowOverviewReleaseReadinessArtifactValidationActionPlanRenderState: (actionPlan) => (
shellViewModel.createWorkflowOverviewReleaseReadinessArtifactValidationActionPlanRenderState(actionPlan)
),
getWorkflowOverviewReleaseReadinessArtifactValidationActionPlanDomContract: (options) => (
shellViewModel.createWorkflowOverviewReleaseReadinessArtifactValidationActionPlanDomContract(options)
),
getWorkflowOverviewReleaseReadinessArtifactValidationActionPlanDomReadiness: (options) => (
shellViewModel.createWorkflowOverviewReleaseReadinessArtifactValidationActionPlanDomReadiness(
options ?? { documentRef: document },
)
),
renderWorkflowOverviewReleaseReadinessArtifactValidationActionPlanState: (renderState, options) => (
shellViewModel.renderWorkflowOverviewReleaseReadinessArtifactValidationActionPlanState(
renderState,
options ?? { documentRef: document },
)
),
mountWorkflowOverviewReleaseReadinessArtifactValidationActionPlanState: (options) => (
shellViewModel.mountWorkflowOverviewReleaseReadinessArtifactValidationActionPlanState(
options ?? { documentRef: document },
)
),
getWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryViewModel: (validation) => (
shellViewModel.createWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryViewModel(validation)
),
getWorkflowOverviewReleaseReadinessArtifactUrlGateExecutionSummaryViewModel: (workflow) => (
shellViewModel.createWorkflowOverviewReleaseReadinessArtifactUrlGateExecutionSummaryViewModel(workflow)
),
getWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryRenderState: (summaryViewModel) => (
shellViewModel.createWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryRenderState(summaryViewModel)
),
getWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryDomContract: (options) => (
shellViewModel.createWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryDomContract(options)
),
getWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryDomReadiness: (options) => (
shellViewModel.createWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryDomReadiness(
options ?? { documentRef: document },
)
),
renderWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryState: (renderState, options) => (
shellViewModel.renderWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryState(
renderState,
options ?? { documentRef: document },
)
),
mountWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryState: (options) => (
shellViewModel.mountWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryState(
options ?? { documentRef: document },
)
),
getWorkflowOverviewEmbeddingReport: (embeddingResult, contract) => (
shellViewModel.createWorkflowOverviewEmbeddingReport(embeddingResult, contract)
),
getWorkflowOverviewEmbeddingDisplayViewModel: (report) => (
shellViewModel.createWorkflowOverviewEmbeddingDisplayViewModel(report)
),
getWorkflowOverviewEmbeddingRenderState: (displayViewModel) => (
shellViewModel.createWorkflowOverviewEmbeddingRenderState(displayViewModel)
),
getWorkflowOverviewEmbeddingDomContract: (options) => (
shellViewModel.createWorkflowOverviewEmbeddingDomContract(options)
),
getWorkflowOverviewEmbeddingDomReadiness: (options) => (
shellViewModel.createWorkflowOverviewEmbeddingDomReadiness(options)
),
renderWorkflowOverviewEmbeddingState: (renderState, options) => (
shellViewModel.renderWorkflowOverviewEmbeddingState(renderState, options ?? { documentRef: document })
),
mountWorkflowOverviewEmbeddingState: (options) => (
shellViewModel.mountWorkflowOverviewEmbeddingState(options ?? {
documentRef: document,
})
),
getWorkflowOverviewEmbeddingMountDisplayViewModel: (mountResult) => (
shellViewModel.createWorkflowOverviewEmbeddingMountDisplayViewModel(mountResult)
),
getWorkflowOverviewEmbeddingMountRenderState: (displayViewModel) => (
shellViewModel.createWorkflowOverviewEmbeddingMountRenderState(displayViewModel)
),
getWorkflowOverviewEmbeddingMountDomContract: (options) => (
shellViewModel.createWorkflowOverviewEmbeddingMountDomContract(options)
),
getWorkflowOverviewEmbeddingMountDomReadiness: (options) => (
shellViewModel.createWorkflowOverviewEmbeddingMountDomReadiness(options)
),
renderWorkflowOverviewEmbeddingMountState: (renderState, options) => (
shellViewModel.renderWorkflowOverviewEmbeddingMountState(renderState, options ?? { documentRef: document })
),
mountWorkflowOverviewEmbeddingMountState: (options) => (
shellViewModel.mountWorkflowOverviewEmbeddingMountState(options ?? {
documentRef: document,
})
),
isSupportedShellSessionHandoffPackage: shellViewModel.isSupportedShellSessionHandoffPackage,
getLauncherLinks: () => shellViewModel.launcherLinks,
getControlPageApiMethods: () => shellViewModel.controlPage.browserApiMethods,
};

View File

@@ -3,6 +3,7 @@ export function createIniPanelStateSummary({
machine = null,
machineFiles = null,
sessionSnapshot = null,
sessionReadiness = null,
sessionLoad = null,
runReadiness = null,
run = null,
@@ -17,6 +18,7 @@ export function createIniPanelStateSummary({
machine,
machineFiles,
sessionSnapshot,
sessionReadiness,
sessionLoad,
runReadiness,
run,
@@ -29,6 +31,7 @@ export function createMachineSessionStateSnapshot({
machine = null,
machineFiles = null,
sessionSnapshot = null,
sessionReadiness = null,
sessionLoad = null,
runReadiness = null,
selectedProgram = null,
@@ -44,6 +47,7 @@ export function createMachineSessionStateSnapshot({
machine,
machineFiles,
sessionSnapshot,
sessionReadiness,
sessionLoad,
runReadiness,
run,
@@ -72,6 +76,7 @@ export function createMachineSessionStateReport(state = {}) {
const badges = state.badges ?? {};
const fields = state.fields ?? {};
const sessionSnapshot = state.sessionSnapshot ?? {};
const sessionReadiness = state.sessionReadiness ?? null;
const sessionLoad = state.sessionLoad ?? {};
const runReadiness = state.runReadiness ?? {};
const selectedProgram = state.selectedProgram ?? {};
@@ -90,6 +95,7 @@ export function createMachineSessionStateReport(state = {}) {
toolTable: fields.sessionToolTable ?? sessionSnapshot.toolTableOpfsPath ?? sessionLoad.toolTableWasmPath ?? null,
gcode: fields.sessionGcode ?? sessionSnapshot.gcodeOpfsPath ?? null,
},
sessionReadiness,
loaded: {
iniWasmPath: sessionLoad.iniWasmPath ?? null,
parameterWasmPath: sessionLoad.parameterWasmPath ?? null,
@@ -141,6 +147,7 @@ export function createMachineSessionWorkflowStatus(state = {}) {
return {
readiness: report.readiness,
overview,
sessionReadiness: report.sessionReadiness ?? null,
runReadiness: report.runReadiness,
lastAction: report.status.lastAction,
error: report.status.error,
@@ -149,6 +156,156 @@ export function createMachineSessionWorkflowStatus(state = {}) {
};
}
export function createMachineSessionPersistenceSummary({
state = {},
report = createMachineSessionStateReport(state),
workflowStatus = createMachineSessionWorkflowStatus(state),
readonlyStatus = null,
handoffSummary = null,
handoffWorkflowSummary = null,
} = {}) {
const readiness = report.readiness ?? {};
const machineFiles = state.machineFiles ?? {};
const session = report.session ?? {};
const loaded = report.loaded ?? {};
const sessionReadiness = report.sessionReadiness ?? workflowStatus.sessionReadiness ?? null;
const opfsReady = String(readiness.opfs ?? "").toLowerCase().includes("ready");
const machineFileLabels = [
machineFiles.iniOpfsPath || session.ini ? "ini" : null,
machineFiles.parameterOpfsPath || session.parameters ? "parameters" : null,
machineFiles.toolTableOpfsPath || session.toolTable ? "tool-table" : null,
machineFiles.gcodeOpfsPath || session.gcode ? "g-code" : null,
].filter(Boolean);
const machineFilesReady = machineFileLabels.length > 0;
const snapshotReady = Boolean(session.snapshotLabel);
const sessionReadinessReady = sessionReadiness?.ready === true;
const sessionLoadReady = Boolean(loaded.iniWasmPath);
const readonlyStatusValue = readonlyStatus
? (readonlyStatus.ready === true ? "ready" : readonlyStatus.phase ?? "available")
: "not-provided";
const handoffValue = handoffWorkflowSummary
? (handoffWorkflowSummary.ready === true ? "ready" : handoffWorkflowSummary.phase ?? "blocked")
: (
handoffSummary
? (handoffSummary.ready === true ? "ready" : handoffSummary.phase ?? "blocked")
: "not-provided"
);
const checks = {
opfs: opfsReady,
machineFiles: machineFilesReady,
sessionSnapshot: snapshotReady,
sessionReadiness: sessionReadinessReady,
sessionLoad: sessionLoadReady,
};
const missing = Object.entries(checks)
.filter(([, passed]) => !passed)
.map(([name]) => name);
const ready = missing.length === 0;
return {
apiName: "machine-session-persistence-summary",
summaryVersion: 1,
phase: ready ? "ready" : "waiting",
ready,
checks,
missing,
counts: {
machineFiles: machineFileLabels.length,
missing: missing.length,
},
rows: [
{
id: "opfs",
label: "OPFS",
value: readiness.opfs ?? "missing",
},
{
id: "machine-files",
label: "Machine files",
value: machineFileLabels.length > 0 ? machineFileLabels.join(", ") : "missing",
},
{
id: "session-snapshot",
label: "Session snapshot",
value: session.snapshotLabel ?? "missing",
},
{
id: "session-readiness",
label: "Session readiness",
value: sessionReadiness
? (sessionReadiness.ready ? "ready" : `blocked: ${sessionReadiness.missing?.join(", ") || "unknown"}`)
: "missing",
},
{
id: "session-load",
label: "Session load",
value: loaded.iniWasmPath ? `loaded: ${loaded.iniWasmPath}` : "missing",
},
{
id: "readonly-status",
label: "Readonly status",
value: readonlyStatusValue,
},
{
id: "handoff-readiness",
label: "Handoff readiness",
value: handoffValue,
},
],
};
}
export function createMachineSessionPersistenceDisplayViewModel(
summary = createMachineSessionPersistenceSummary(),
) {
const missing = Array.isArray(summary?.missing) ? summary.missing : [];
const rows = Array.isArray(summary?.rows)
? summary.rows.map(({ id, label, value }) => ({
id,
label,
value: value == null ? "" : String(value),
}))
: [];
return {
phase: summary?.phase === "ready" ? "ready" : "waiting",
title: "OPFS/session persistence",
statusText: summary?.ready ? "Ready" : "Waiting",
detailText: missing.length > 0 ? missing.join(", ") : "No missing persistence requirements",
rows,
};
}
export function createMachineSessionPersistenceRenderState(
displayViewModel = createMachineSessionPersistenceDisplayViewModel(),
) {
const rows = Array.isArray(displayViewModel?.rows)
? displayViewModel.rows.map(({ id, label, value }) => ({
id,
label,
value: value == null ? "" : String(value),
}))
: [];
const ready = displayViewModel?.phase === "ready" && displayViewModel?.statusText === "Ready";
const statusText = displayViewModel?.statusText ?? (ready ? "Ready" : "Waiting");
const detailText = displayViewModel?.detailText ?? "No missing persistence requirements";
return {
apiName: "machine-session-persistence-render-state",
renderStateVersion: 1,
phase: ready ? "ready" : "waiting",
ready,
title: displayViewModel?.title ?? "OPFS/session persistence",
statusText,
detailText,
statusLine: `${statusText}: ${detailText}`,
rows,
dataset: {
persistencePhase: ready ? "ready" : "waiting",
persistenceReady: ready ? "true" : "false",
persistenceScope: "opfs-session",
},
};
}
export function createMachineSessionWorkflowOverview(report = {}) {
const readiness = report.readiness ?? {};
const loaded = report.loaded ?? {};

View File

@@ -4,12 +4,17 @@ export function createMachineSessionReadonlyStatus({
controlView = null,
} = {}) {
const overview = stateBundle?.overview ?? workflowStatus?.overview ?? null;
const sessionReadiness = workflowStatus?.sessionReadiness
?? stateBundle?.workflowStatus?.sessionReadiness
?? stateBundle?.report?.sessionReadiness
?? null;
return {
stateBundle,
workflowStatus,
overview,
controlView,
report: stateBundle?.report ?? null,
sessionReadiness,
runReadiness: workflowStatus?.runReadiness ?? stateBundle?.report?.runReadiness ?? null,
run: workflowStatus?.run ?? stateBundle?.report?.run ?? null,
session: workflowStatus?.session ?? stateBundle?.report?.session ?? null,
@@ -42,6 +47,7 @@ export function createMachineSessionReadonlyStatusApiManifest() {
"overview",
"controlView",
"report",
"sessionReadiness",
"runReadiness",
"run",
"session",

View File

@@ -52,6 +52,47 @@ export function machineSessionLoadWorkflowLogLines(summary) {
];
}
export function createMachineSessionReadinessFieldState(readiness) {
return {
sessionReadinessPhase: readiness.phase,
sessionReadinessReady: readiness.ready ? "ready" : "blocked",
sessionReadinessMissing: readiness.missing.length === 0 ? "none" : readiness.missing.join(", "),
};
}
export function machineSessionReadinessLogLines(readiness) {
const checks = Array.isArray(readiness.checks) ? readiness.checks : [];
return [
`Session readiness: ${readiness.ready ? "ready" : "blocked"}`,
...checks.map((check) => {
const status = check.ok ? "ok" : `blocked (${check.error})`;
return `${check.name}: ${check.path} -> ${status}`;
}),
...(readiness.snapshotCheck
? [
`snapshot: ${readiness.snapshotCheck.path} -> ${
readiness.snapshotCheck.ok ? "ok" : `blocked (${readiness.snapshotCheck.error})`
}`,
]
: []),
];
}
export async function readMachineSessionReadinessWorkflow({
readiness,
readReadiness,
} = {}) {
const sessionReadiness = readiness ?? await readReadiness?.();
if (!sessionReadiness) {
throw new Error("A machine session readiness reader is required.");
}
return {
readiness: sessionReadiness,
fields: createMachineSessionReadinessFieldState(sessionReadiness),
logLines: machineSessionReadinessLogLines(sessionReadiness),
};
}
export async function loadMachineSessionIntoWasmWorkflow({ loadSession }) {
if (typeof loadSession !== "function") {
throw new Error("A machine session loader is required.");
@@ -114,6 +155,7 @@ export async function restoreMachineSessionSnapshotWorkflow({
snapshotLabel,
loadMachineSessionSnapshot,
loadMachineTextFiles,
readReadiness = null,
loadSession = null,
}) {
const snapshot = await loadMachineSessionSnapshot(
@@ -129,21 +171,36 @@ export async function restoreMachineSessionSnapshotWorkflow({
editorIniText: files.ini,
summary,
fields,
readiness: null,
loadSummary: null,
loadedSession: null,
logLines: sessionSnapshotRestoreLogLines(summary),
};
if (readReadiness) {
const readinessWorkflow = await readMachineSessionReadinessWorkflow({ readReadiness });
result.readiness = readinessWorkflow.readiness;
result.fields = {
...result.fields,
...readinessWorkflow.fields,
};
result.logLines = [
...result.logLines,
...readinessWorkflow.logLines,
];
}
if (loadSession) {
const loadWorkflow = await loadMachineSessionIntoWasmWorkflow({ loadSession });
result.loadedSession = loadWorkflow.loadedSession;
result.loadSummary = loadWorkflow.loadSummary;
result.fields = {
...fields,
...(result.readiness ? createMachineSessionReadinessFieldState(result.readiness) : {}),
...loadWorkflow.fields,
sessionGcode: summary.gcodeOpfsPath,
};
result.logLines = [
...sessionSnapshotSummaryLogLines(summary),
...(result.readiness ? machineSessionReadinessLogLines(result.readiness) : []),
...loadWorkflow.logLines,
`G-code: ${summary.gcodeOpfsPath ?? "-"}`,
];

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,387 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>LinuxCNC INI Workflow Overview</title>
<style>
:root {
color-scheme: light;
--bg: #f2f5f8;
--panel: #ffffff;
--border: #cfd8e1;
--text: #17212b;
--muted: #5a6875;
--accent: #1366a6;
}
* { box-sizing: border-box; }
body {
margin: 0;
font-family: "Segoe UI", "Noto Sans", sans-serif;
background: var(--bg);
color: var(--text);
}
main {
max-width: 960px;
margin: 0 auto;
padding: 24px;
display: grid;
gap: 16px;
}
.panel {
background: var(--panel);
border: 1px solid var(--border);
border-radius: 8px;
box-shadow: 0 10px 30px rgba(17, 34, 51, 0.06);
padding: 16px;
display: grid;
gap: 14px;
}
h1 {
margin: 0;
font-size: 18px;
font-weight: 600;
}
p {
margin: 0;
color: var(--muted);
font: 13px/1.5 "Segoe UI", "Noto Sans", sans-serif;
}
.overview-status {
border-top: 1px solid var(--border);
padding-top: 12px;
display: grid;
gap: 8px;
}
.overview-status dl {
margin: 0;
display: grid;
grid-template-columns: minmax(120px, 180px) 1fr;
gap: 6px 12px;
font: 13px/1.4 "Segoe UI", "Noto Sans", sans-serif;
}
.overview-status dt {
color: var(--muted);
}
.overview-status dd {
margin: 0;
color: var(--text);
overflow-wrap: anywhere;
}
</style>
</head>
<body>
<main>
<section class="panel">
<h1>LinuxCNC INI Workflow Overview</h1>
<p>Read-only shell handoff overview for mount, readonly status, and overall workflow readiness.</p>
<section class="overview-status" aria-label="Shell handoff workflow overview" data-handoff-workflow>
<p data-handoff-workflow-status>Checking shell handoff workflow summary.</p>
<dl data-handoff-workflow-rows></dl>
</section>
</section>
</main>
<script type="module">
import {
createIniPanelShellViewModel,
createIniPanelShellSessionHandoffWorkflowDomContract,
createIniPanelShellSessionHandoffWorkflowDomReadiness,
createIniPanelShellSessionHandoffWorkflowRenderState,
createIniPanelShellSessionReadinessWorkflowReport,
createIniPanelShellWorkflowOverviewContract,
createIniPanelShellWorkflowOverviewReadiness,
createIniPanelShellWorkflowOverviewEmbeddingReport,
createIniPanelShellWorkflowOverviewEmbeddingDisplayViewModel,
createIniPanelShellWorkflowOverviewEmbeddingRenderState,
createIniPanelShellWorkflowOverviewEmbeddingDomContract,
createIniPanelShellWorkflowOverviewEmbeddingDomReadiness,
mountIniPanelShellWorkflowOverviewEmbedding,
createMachineSessionPersistenceSummary,
renderIniPanelShellWorkflowOverviewEmbeddingState,
mountIniPanelShellWorkflowOverviewEmbeddingState,
createIniPanelShellWorkflowOverviewEmbeddingMountDisplayViewModel,
createIniPanelShellWorkflowOverviewEmbeddingMountRenderState,
createIniPanelShellWorkflowOverviewEmbeddingMountDomContract,
createIniPanelShellWorkflowOverviewEmbeddingMountDomReadiness,
renderIniPanelShellWorkflowOverviewEmbeddingMountState,
mountIniPanelShellWorkflowOverviewEmbeddingMountState,
validateIniPanelShellWorkflowOverviewReleaseReadinessArtifactJson,
runIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflow,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflowSummaryViewModel,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflowActionPlan,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflowRenderState,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflowDomContract,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflowDomReadiness,
renderIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflowState,
mountIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflowState,
loadIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrl,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowSummaryViewModel,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowActionPlan,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowRenderState,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowDomContract,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowDomReadiness,
renderIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowState,
mountIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowState,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationSummaryViewModel,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationActionPlan,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryViewModel,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlGateExecutionSummaryViewModel,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryRenderState,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryDomContract,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryDomReadiness,
renderIniPanelShellWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryState,
mountIniPanelShellWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryState,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationActionPlanRenderState,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationActionPlanDomContract,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationActionPlanDomReadiness,
renderIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationActionPlanState,
mountIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationActionPlanState,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactDisplayViewModel,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactRenderState,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactDomContract,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactDomReadiness,
renderIniPanelShellWorkflowOverviewReleaseReadinessArtifactState,
mountIniPanelShellWorkflowOverviewReleaseReadinessArtifactState,
renderIniPanelShellSessionHandoffWorkflowState,
mountIniPanelShellSessionHandoffWorkflow,
} from "./ui-shell.js";
const shellViewModel = createIniPanelShellViewModel();
const workflowRenderState = shellViewModel.shellSessionHandoffWorkflowRenderState;
renderIniPanelShellSessionHandoffWorkflowState(workflowRenderState, { documentRef: document });
window.linuxCncIniPanelWorkflowOverviewApi = {
getWorkflowOverviewRenderState: () => shellViewModel.shellSessionHandoffWorkflowRenderState,
getWorkflowOverviewDisplayViewModel: () => shellViewModel.shellSessionHandoffWorkflowDisplayViewModel,
getWorkflowOverviewSummary: () => shellViewModel.shellSessionHandoffWorkflowSummary,
getWorkflowOverviewSessionReadinessReport: (options) => (
createIniPanelShellSessionReadinessWorkflowReport(options ?? {
workflowSummary: shellViewModel.shellSessionHandoffWorkflowSummary,
})
),
getWorkflowOverviewApiSurfaceInventory: (options) => (
shellViewModel.createShellApiSurfaceInventory(options ?? {
launchApiManifest: shellViewModel.launchApiManifest,
controlPageApiManifest: shellViewModel.controlPage.browserApiManifest,
workflowOverviewContract: shellViewModel.shellWorkflowOverviewContract,
sessionReadinessWorkflowReport: shellViewModel.shellSessionReadinessWorkflowReport,
})
),
getWorkflowOverviewPersistenceSummary: (options) => (
createMachineSessionPersistenceSummary(options)
),
validateWorkflowOverviewReleaseReadinessArtifactJson: (artifactJson) => (
validateIniPanelShellWorkflowOverviewReleaseReadinessArtifactJson(artifactJson)
),
runWorkflowOverviewReleaseReadinessArtifactJsonWorkflow: (options) => (
runIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflow(options)
),
getWorkflowOverviewReleaseReadinessArtifactJsonWorkflowSummaryViewModel: (workflow) => (
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflowSummaryViewModel(workflow)
),
getWorkflowOverviewReleaseReadinessArtifactJsonWorkflowActionPlan: (workflow) => (
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflowActionPlan(workflow)
),
getWorkflowOverviewReleaseReadinessArtifactJsonWorkflowRenderState: (summaryViewModel) => (
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflowRenderState(summaryViewModel)
),
getWorkflowOverviewReleaseReadinessArtifactJsonWorkflowDomContract: (options) => (
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflowDomContract(options)
),
getWorkflowOverviewReleaseReadinessArtifactJsonWorkflowDomReadiness: (options) => (
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflowDomReadiness(
options ?? { documentRef: document },
)
),
renderWorkflowOverviewReleaseReadinessArtifactJsonWorkflowState: (renderState, options) => (
renderIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflowState(
renderState,
options ?? { documentRef: document },
)
),
mountWorkflowOverviewReleaseReadinessArtifactJsonWorkflowState: (options) => (
mountIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflowState(
options ?? { documentRef: document },
)
),
loadWorkflowOverviewReleaseReadinessArtifactUrl: (options) => (
loadIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrl(options)
),
getWorkflowOverviewReleaseReadinessArtifactUrlWorkflowSummaryViewModel: (workflow) => (
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowSummaryViewModel(workflow)
),
getWorkflowOverviewReleaseReadinessArtifactUrlWorkflowActionPlan: (workflow) => (
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowActionPlan(workflow)
),
getWorkflowOverviewReleaseReadinessArtifactUrlWorkflowRenderState: (summaryViewModel) => (
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowRenderState(summaryViewModel)
),
getWorkflowOverviewReleaseReadinessArtifactUrlWorkflowDomContract: (options) => (
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowDomContract(options)
),
getWorkflowOverviewReleaseReadinessArtifactUrlWorkflowDomReadiness: (options) => (
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowDomReadiness(
options ?? { documentRef: document },
)
),
renderWorkflowOverviewReleaseReadinessArtifactUrlWorkflowState: (renderState, options) => (
renderIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowState(
renderState,
options ?? { documentRef: document },
)
),
mountWorkflowOverviewReleaseReadinessArtifactUrlWorkflowState: (options) => (
mountIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowState(
options ?? { documentRef: document },
)
),
getWorkflowOverviewReleaseReadinessArtifactValidationSummaryViewModel: (validation) => (
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationSummaryViewModel(validation)
),
getWorkflowOverviewReleaseReadinessArtifactValidationActionPlan: (validation) => (
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationActionPlan(validation)
),
getWorkflowOverviewReleaseReadinessArtifactValidationActionPlanRenderState: (actionPlan) => (
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationActionPlanRenderState(actionPlan)
),
getWorkflowOverviewReleaseReadinessArtifactValidationActionPlanDomContract: (options) => (
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationActionPlanDomContract(options)
),
getWorkflowOverviewReleaseReadinessArtifactValidationActionPlanDomReadiness: (options) => (
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationActionPlanDomReadiness(
options ?? { documentRef: document },
)
),
renderWorkflowOverviewReleaseReadinessArtifactValidationActionPlanState: (renderState, options) => (
renderIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationActionPlanState(
renderState,
options ?? { documentRef: document },
)
),
mountWorkflowOverviewReleaseReadinessArtifactValidationActionPlanState: (options) => (
mountIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationActionPlanState(
options ?? { documentRef: document },
)
),
getWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryViewModel: (validation) => (
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryViewModel(validation)
),
getWorkflowOverviewReleaseReadinessArtifactUrlGateExecutionSummaryViewModel: (workflow) => (
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlGateExecutionSummaryViewModel(workflow)
),
getWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryRenderState: (summaryViewModel) => (
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryRenderState(summaryViewModel)
),
getWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryDomContract: (options) => (
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryDomContract(options)
),
getWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryDomReadiness: (options) => (
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryDomReadiness(
options ?? { documentRef: document },
)
),
renderWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryState: (renderState, options) => (
renderIniPanelShellWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryState(
renderState,
options ?? { documentRef: document },
)
),
mountWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryState: (options) => (
mountIniPanelShellWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryState(
options ?? { documentRef: document },
)
),
getWorkflowOverviewReleaseReadinessArtifactDisplayViewModel: (validation) => (
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactDisplayViewModel(validation)
),
getWorkflowOverviewReleaseReadinessArtifactRenderState: (displayViewModel) => (
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactRenderState(displayViewModel)
),
getWorkflowOverviewReleaseReadinessArtifactDomContract: (options) => (
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactDomContract(options)
),
getWorkflowOverviewReleaseReadinessArtifactDomReadiness: (options) => (
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactDomReadiness(options ?? { documentRef: document })
),
renderWorkflowOverviewReleaseReadinessArtifactState: (renderState, options) => (
renderIniPanelShellWorkflowOverviewReleaseReadinessArtifactState(renderState, options ?? { documentRef: document })
),
mountWorkflowOverviewReleaseReadinessArtifactState: (options) => (
mountIniPanelShellWorkflowOverviewReleaseReadinessArtifactState(options ?? { documentRef: document })
),
getWorkflowOverviewDomContract: (options) => (
createIniPanelShellSessionHandoffWorkflowDomContract(options)
),
getWorkflowOverviewDomReadiness: (options) => (
createIniPanelShellSessionHandoffWorkflowDomReadiness(options ?? { documentRef: document })
),
renderWorkflowOverview: (renderState, options) => (
renderIniPanelShellSessionHandoffWorkflowState(
renderState ?? shellViewModel.shellSessionHandoffWorkflowRenderState,
options ?? { documentRef: document },
)
),
mountWorkflowOverview: (options) => (
mountIniPanelShellSessionHandoffWorkflow(options ?? {
renderState: shellViewModel.shellSessionHandoffWorkflowRenderState,
documentRef: document,
})
),
createWorkflowOverviewRenderState: createIniPanelShellSessionHandoffWorkflowRenderState,
getWorkflowOverviewContract: () => createIniPanelShellWorkflowOverviewContract(),
getWorkflowOverviewReadinessFromApi: () => createIniPanelShellWorkflowOverviewReadiness({
overviewApi: window.linuxCncIniPanelWorkflowOverviewApi,
}),
mountWorkflowOverviewEmbedding: (options) => (
mountIniPanelShellWorkflowOverviewEmbedding({
overviewApi: window.linuxCncIniPanelWorkflowOverviewApi,
...(options ?? {}),
})
),
getWorkflowOverviewEmbeddingReport: (embeddingResult, contract) => (
createIniPanelShellWorkflowOverviewEmbeddingReport(embeddingResult, contract)
),
getWorkflowOverviewEmbeddingDisplayViewModel: (report) => (
createIniPanelShellWorkflowOverviewEmbeddingDisplayViewModel(report)
),
getWorkflowOverviewEmbeddingRenderState: (displayViewModel) => (
createIniPanelShellWorkflowOverviewEmbeddingRenderState(displayViewModel)
),
getWorkflowOverviewEmbeddingDomContract: (options) => (
createIniPanelShellWorkflowOverviewEmbeddingDomContract(options)
),
getWorkflowOverviewEmbeddingDomReadiness: (options) => (
createIniPanelShellWorkflowOverviewEmbeddingDomReadiness(options ?? { documentRef: document })
),
renderWorkflowOverviewEmbeddingState: (renderState, options) => (
renderIniPanelShellWorkflowOverviewEmbeddingState(renderState, options ?? { documentRef: document })
),
mountWorkflowOverviewEmbeddingState: (options) => (
mountIniPanelShellWorkflowOverviewEmbeddingState(options ?? {
documentRef: document,
})
),
getWorkflowOverviewEmbeddingMountDisplayViewModel: (mountResult) => (
createIniPanelShellWorkflowOverviewEmbeddingMountDisplayViewModel(mountResult)
),
getWorkflowOverviewEmbeddingMountRenderState: (displayViewModel) => (
createIniPanelShellWorkflowOverviewEmbeddingMountRenderState(displayViewModel)
),
getWorkflowOverviewEmbeddingMountDomContract: (options) => (
createIniPanelShellWorkflowOverviewEmbeddingMountDomContract(options)
),
getWorkflowOverviewEmbeddingMountDomReadiness: (options) => (
createIniPanelShellWorkflowOverviewEmbeddingMountDomReadiness(options ?? { documentRef: document })
),
renderWorkflowOverviewEmbeddingMountState: (renderState, options) => (
renderIniPanelShellWorkflowOverviewEmbeddingMountState(renderState, options ?? { documentRef: document })
),
mountWorkflowOverviewEmbeddingMountState: (options) => (
mountIniPanelShellWorkflowOverviewEmbeddingMountState(options ?? {
documentRef: document,
})
),
};
</script>
</body>
</html>

View File

@@ -0,0 +1,338 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>LinuxCNC Browser Simulation</title>
<style>
:root {
color-scheme: light;
--page: #eef2f5;
--surface: #ffffff;
--line: #c8d3dc;
--text: #1a252f;
--muted: #596875;
--blue: #1263a3;
--green: #188160;
--steel: #6f7f8d;
--warn: #a9561b;
}
* { box-sizing: border-box; }
body {
margin: 0;
min-height: 100vh;
font-family: "Segoe UI", "Noto Sans", sans-serif;
color: var(--text);
background: var(--page);
}
main {
display: grid;
grid-template-rows: auto minmax(0, 1fr);
min-height: 100vh;
}
header {
border-bottom: 1px solid var(--line);
background: var(--surface);
padding: 14px 20px;
display: grid;
grid-template-columns: minmax(0, 1fr) auto;
gap: 16px;
align-items: center;
}
h1 {
margin: 0;
font-size: 18px;
font-weight: 650;
}
.status-strip {
display: flex;
gap: 8px;
align-items: center;
flex-wrap: wrap;
justify-content: end;
}
.badge {
border: 1px solid var(--line);
border-radius: 6px;
padding: 5px 8px;
background: #f8fafb;
color: var(--muted);
font: 12px/1.2 "Segoe UI", sans-serif;
}
.badge strong {
color: var(--green);
font-weight: 700;
}
.workspace {
display: grid;
grid-template-columns: minmax(280px, 0.75fr) minmax(420px, 1.35fr) minmax(300px, 0.9fr);
min-height: 0;
}
section {
min-width: 0;
min-height: 0;
border-right: 1px solid var(--line);
background: var(--surface);
display: grid;
grid-template-rows: auto minmax(0, 1fr);
}
section:last-child { border-right: 0; }
.section-head {
border-bottom: 1px solid var(--line);
padding: 12px 14px;
display: grid;
gap: 4px;
}
h2 {
margin: 0;
font-size: 14px;
font-weight: 650;
}
.hint {
margin: 0;
color: var(--muted);
font-size: 12px;
line-height: 1.4;
}
.program-lines {
overflow: auto;
padding: 10px 0;
background: #fbfcfd;
font: 13px/1.45 "SFMono-Regular", "Consolas", monospace;
}
.program-lines div {
display: grid;
grid-template-columns: 48px minmax(0, 1fr);
gap: 10px;
padding: 4px 12px;
border-left: 3px solid transparent;
}
.program-lines div[data-active="true"] {
background: #e9f4ef;
border-left-color: var(--green);
}
.program-lines span {
color: var(--steel);
text-align: right;
user-select: none;
}
.program-lines code {
color: var(--text);
white-space: pre-wrap;
overflow-wrap: anywhere;
}
.preview {
background: #f8fafb;
display: grid;
grid-template-rows: minmax(0, 1fr) auto;
}
.toolpath-wrap {
min-height: 0;
padding: 18px;
display: grid;
}
svg {
width: 100%;
height: 100%;
min-height: 320px;
border: 1px solid var(--line);
background: #ffffff;
}
.grid-line {
stroke: #edf1f4;
stroke-width: 0.01;
}
.toolpath {
fill: none;
stroke: var(--blue);
stroke-width: 0.035;
stroke-linecap: round;
stroke-linejoin: round;
}
.toolhead {
fill: var(--warn);
stroke: #ffffff;
stroke-width: 0.025;
}
.readout {
border-top: 1px solid var(--line);
padding: 12px 14px;
display: grid;
gap: 10px;
background: var(--surface);
}
.axis-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 8px;
}
.axis-grid div {
border: 1px solid var(--line);
border-radius: 6px;
padding: 8px;
display: grid;
gap: 4px;
background: #fbfcfd;
}
.axis-grid span:first-child {
color: var(--muted);
font-size: 11px;
font-weight: 700;
}
.axis-grid span:last-child {
font: 14px/1.2 "SFMono-Regular", "Consolas", monospace;
}
dl {
margin: 0;
display: grid;
grid-template-columns: 128px minmax(0, 1fr);
gap: 7px 12px;
font-size: 12px;
}
dt { color: var(--muted); }
dd {
margin: 0;
overflow-wrap: anywhere;
font-family: "SFMono-Regular", "Consolas", monospace;
}
.motion-table-wrap {
overflow: auto;
min-height: 0;
}
table {
width: 100%;
border-collapse: collapse;
font: 12px/1.4 "Segoe UI", sans-serif;
}
th, td {
border-bottom: 1px solid #edf1f4;
padding: 7px 8px;
text-align: left;
vertical-align: top;
}
th {
position: sticky;
top: 0;
background: #f8fafb;
z-index: 1;
color: var(--muted);
font-size: 11px;
text-transform: uppercase;
}
td:nth-child(3), td:nth-child(4) {
font-family: "SFMono-Regular", "Consolas", monospace;
}
pre {
margin: 0;
overflow: auto;
padding: 12px;
background: #111820;
color: #dbe7ef;
font: 11px/1.45 "SFMono-Regular", "Consolas", monospace;
max-height: 170px;
}
@media (max-width: 1040px) {
.workspace { grid-template-columns: 1fr; }
section { border-right: 0; border-bottom: 1px solid var(--line); }
svg { min-height: 280px; }
}
@media (max-width: 560px) {
header { grid-template-columns: 1fr; }
.status-strip { justify-content: start; }
.axis-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
dl { grid-template-columns: 1fr; }
}
</style>
</head>
<body data-simulation-ready="false">
<main>
<header>
<h1>LinuxCNC Browser Simulation</h1>
<div class="status-strip">
<span class="badge">Simulation <strong data-simulation-status>loading</strong></span>
<span class="badge" data-runtime-status>LinuxCNC interpreter WASM</span>
</div>
</header>
<div class="workspace">
<section aria-label="G-code program">
<div class="section-head">
<h2>Program</h2>
<p class="hint">Executed through the LinuxCNC-backed WASM interpreter.</p>
</div>
<div class="program-lines" data-program-lines></div>
</section>
<section aria-label="Toolpath preview">
<div class="section-head">
<h2>Toolpath Preview</h2>
<p class="hint">Rendered from LinuxCNC canonical motion events.</p>
</div>
<div class="preview">
<div class="toolpath-wrap">
<svg data-toolpath-svg role="img" aria-label="LinuxCNC canonical toolpath preview" viewBox="-1 -1 2 2">
<line class="grid-line" x1="-10" y1="0" x2="10" y2="0"></line>
<line class="grid-line" x1="0" y1="-10" x2="0" y2="10"></line>
<polyline class="toolpath" data-toolpath-polyline points=""></polyline>
<circle class="toolhead" data-toolpath-head cx="0" cy="0" r="0.055"></circle>
</svg>
</div>
<div class="readout">
<div class="axis-grid">
<div><span>X</span><span data-axis="x">0.000</span></div>
<div><span>Y</span><span data-axis="y">0.000</span></div>
<div><span>Z</span><span data-axis="z">0.000</span></div>
<div><span>A</span><span data-axis="a">0.000</span></div>
<div><span>B</span><span data-axis="b">0.000</span></div>
<div><span>C</span><span data-axis="c">0.000</span></div>
</div>
<dl data-simulation-summary-rows></dl>
</div>
</div>
</section>
<section aria-label="Machine state">
<div class="section-head">
<h2>Machine State</h2>
<p class="hint">Active line, statement, motion rows, and canonical output.</p>
</div>
<div class="motion-table-wrap">
<table>
<thead>
<tr>
<th>Motion</th>
<th>Line</th>
<th>Position</th>
<th>Statement</th>
</tr>
</thead>
<tbody data-motion-rows></tbody>
</table>
<div class="readout">
<dl>
<dt>Active line</dt>
<dd data-active-line>-</dd>
<dt>Statement</dt>
<dd data-active-statement>-</dd>
</dl>
</div>
<pre data-canonical-output></pre>
</div>
</section>
</div>
</main>
<script type="module">
import { runRealBrowserSimulation } from "./simulation-app.js";
try {
const state = await runRealBrowserSimulation({ documentRef: document });
window.linuxCncRealSimulationState = state;
window.linuxCncRealSimulationApi = {
getState: () => window.linuxCncRealSimulationState,
};
} catch (error) {
document.body.dataset.simulationReady = "false";
document.querySelector("[data-simulation-status]").textContent = "failed";
document.querySelector("[data-canonical-output]").textContent = error?.stack ?? error?.message ?? String(error);
throw error;
}
</script>
</body>
</html>

View File

@@ -0,0 +1,239 @@
import { createLinuxCncInterpSdk } from "../../sdk/src/index.js";
export const DEFAULT_SIMULATION_PROGRAM = [
"G0 X0 Y0 Z0",
"G1 X1 Y0 Z0 F100",
"G1 X1 Y1 Z0",
"G1 X0 Y1 Z0",
"G1 X0 Y0 Z0",
"M2",
"",
].join("\n");
const AXES = ["x", "y", "z", "a", "b", "c", "u", "v", "w"];
function readCanonicalNumber(line, name) {
const match = line.match(new RegExp(`(?:^| )${name}=([-+0-9.eE]+)`));
return match ? Number(match[1]) : null;
}
function programLineMap(programText) {
const lines = new Map();
programText.split(/\r?\n/).forEach((line, index) => {
lines.set(index + 1, line.trim() || "(blank)");
});
return lines;
}
export function parseLinuxCncCanonicalMotion(resultText, programText = "") {
const axes = Object.fromEntries(AXES.map((axis) => [axis, 0]));
const sourceLines = programLineMap(programText);
const motion = [];
for (const line of String(resultText).split("\n")) {
const event = line.match(/^canon_event=(STRAIGHT_TRAVERSE|STRAIGHT_FEED|ARC_FEED)\b/);
if (!event) {
continue;
}
for (const axis of AXES) {
const value = readCanonicalNumber(line, axis);
if (value !== null && Number.isFinite(value)) {
axes[axis] = value;
}
}
const sourceLine = readCanonicalNumber(line, "line");
motion.push({
type: event[1],
line: Number.isFinite(sourceLine) ? sourceLine : null,
statement: Number.isFinite(sourceLine) ? (sourceLines.get(sourceLine) ?? "-") : "-",
axes: { ...axes },
raw: line,
});
}
return motion;
}
export function createSimulationSummary({ programText, resultText, motion }) {
const canonicalLines = String(resultText).split("\n").filter((line) => line.startsWith("canon_event="));
const finalAxes = motion.at(-1)?.axes ?? Object.fromEntries(AXES.map((axis) => [axis, 0]));
return {
apiName: "real-browser-simulation-summary",
summaryVersion: 1,
ready: motion.length > 0,
phase: motion.length > 0 ? "ready" : "blocked",
programLineCount: programText.split(/\r?\n/).filter(Boolean).length,
canonicalEventCount: canonicalLines.length,
motionEventCount: motion.length,
finalAxes,
rows: [
{ id: "runtime", label: "Runtime", value: "LinuxCNC interpreter WASM" },
{ id: "program-lines", label: "Program lines", value: `${programText.split(/\r?\n/).filter(Boolean).length}` },
{ id: "canonical-events", label: "Canonical events", value: `${canonicalLines.length}` },
{ id: "motion-events", label: "Motion events", value: `${motion.length}` },
{ id: "final-position", label: "Final XYZ", value: formatPosition(finalAxes) },
],
};
}
export function createToolpathViewBox(motion, padding = 0.25) {
const points = motion.map(({ axes }) => ({ x: axes.x ?? 0, y: axes.y ?? 0 }));
if (points.length === 0) {
return { minX: -1, minY: -1, width: 2, height: 2 };
}
const xs = points.map(({ x }) => x);
const ys = points.map(({ y }) => y);
const minX = Math.min(...xs) - padding;
const maxX = Math.max(...xs) + padding;
const minY = Math.min(...ys) - padding;
const maxY = Math.max(...ys) + padding;
return {
minX,
minY,
width: Math.max(0.1, maxX - minX),
height: Math.max(0.1, maxY - minY),
};
}
export function createToolpathPolylinePoints(motion) {
return motion.map(({ axes }) => `${axes.x ?? 0},${-(axes.y ?? 0)}`).join(" ");
}
export function formatPosition(axes = {}) {
return `X ${formatAxis(axes.x)} Y ${formatAxis(axes.y)} Z ${formatAxis(axes.z)}`;
}
function formatAxis(value) {
return Number.isFinite(value) ? Number(value).toFixed(3) : "0.000";
}
function setText(documentRef, selector, value) {
const node = documentRef.querySelector(selector);
if (node) {
node.textContent = value;
}
}
function renderRows(documentRef, rows) {
const container = documentRef.querySelector("[data-simulation-summary-rows]");
if (!container) {
return;
}
container.textContent = "";
for (const row of rows) {
const term = documentRef.createElement("dt");
term.textContent = row.label;
term.dataset.simulationRow = row.id;
const detail = documentRef.createElement("dd");
detail.textContent = row.value;
detail.dataset.simulationValue = row.id;
container.append(term, detail);
}
}
function renderProgramLines(documentRef, programText, activeLine) {
const container = documentRef.querySelector("[data-program-lines]");
if (!container) {
return;
}
container.textContent = "";
programText.split(/\r?\n/).forEach((line, index) => {
if (!line && index === programText.split(/\r?\n/).length - 1) {
return;
}
const row = documentRef.createElement("div");
row.dataset.programLine = `${index + 1}`;
row.dataset.active = activeLine === index + 1 ? "true" : "false";
const number = documentRef.createElement("span");
number.textContent = `${index + 1}`;
const text = documentRef.createElement("code");
text.textContent = line || " ";
row.append(number, text);
container.append(row);
});
}
function renderAxisReadout(documentRef, axes) {
for (const axis of ["x", "y", "z", "a", "b", "c"]) {
setText(documentRef, `[data-axis="${axis}"]`, formatAxis(axes?.[axis]));
}
}
function renderToolpath(documentRef, motion) {
const svg = documentRef.querySelector("[data-toolpath-svg]");
const polyline = documentRef.querySelector("[data-toolpath-polyline]");
const head = documentRef.querySelector("[data-toolpath-head]");
if (!svg || !polyline || !head) {
return;
}
const viewBox = createToolpathViewBox(motion);
svg.setAttribute(
"viewBox",
`${viewBox.minX} ${-(viewBox.minY + viewBox.height)} ${viewBox.width} ${viewBox.height}`,
);
polyline.setAttribute("points", createToolpathPolylinePoints(motion));
const last = motion.at(-1)?.axes ?? { x: 0, y: 0 };
head.setAttribute("cx", `${last.x ?? 0}`);
head.setAttribute("cy", `${-(last.y ?? 0)}`);
}
function renderMotionTable(documentRef, motion) {
const body = documentRef.querySelector("[data-motion-rows]");
if (!body) {
return;
}
body.textContent = "";
for (const [index, item] of motion.entries()) {
const row = documentRef.createElement("tr");
row.dataset.motionRow = `${index}`;
for (const value of [
item.type,
item.line ?? "-",
formatPosition(item.axes),
item.statement,
]) {
const cell = documentRef.createElement("td");
cell.textContent = `${value}`;
row.append(cell);
}
body.append(row);
}
}
export function renderSimulationState(documentRef, state) {
documentRef.body.dataset.simulationReady = state.summary.ready ? "true" : "false";
setText(documentRef, "[data-simulation-status]", state.summary.ready ? "ready" : "blocked");
setText(documentRef, "[data-runtime-status]", state.summary.rows[0].value);
setText(documentRef, "[data-active-line]", `${state.motion.at(-1)?.line ?? "-"}`);
setText(documentRef, "[data-active-statement]", state.motion.at(-1)?.statement ?? "-");
setText(documentRef, "[data-canonical-output]", state.resultText);
renderRows(documentRef, state.summary.rows);
renderProgramLines(documentRef, state.programText, state.motion.at(-1)?.line ?? null);
renderAxisReadout(documentRef, state.summary.finalAxes);
renderToolpath(documentRef, state.motion);
renderMotionTable(documentRef, state.motion);
}
export async function runRealBrowserSimulation({
documentRef = document,
programText = DEFAULT_SIMULATION_PROGRAM,
interpFactory = createLinuxCncInterpSdk,
} = {}) {
const interp = await interpFactory();
const resultText = interp.runProgram(programText);
const motion = parseLinuxCncCanonicalMotion(resultText, programText);
const summary = createSimulationSummary({ programText, resultText, motion });
const state = {
apiName: "real-browser-simulation-state",
stateVersion: 1,
programText,
resultText,
motion,
summary,
};
renderSimulationState(documentRef, state);
return state;
}

View File

@@ -62,6 +62,9 @@ TOOL_TABLE = browser-tool.tbl
const controlDoc = await loadControlPageFrame();
const controlPageApi = controlDoc.defaultView.linuxCncIniPanelControlPageApi;
const expectedControlPageApiMethods = [
"isSupportedControlPageApiManifest",
"getControlPageApiSchema",
"getControlPageApiManifest",
"getControlPageApiMethods",
"getControlPageContract",
"getControlView",
@@ -72,6 +75,9 @@ TOOL_TABLE = browser-tool.tbl
"renderControlPage",
];
if (
!controlPageApi?.isSupportedControlPageApiManifest ||
!controlPageApi?.getControlPageApiSchema ||
!controlPageApi?.getControlPageApiManifest ||
!controlPageApi?.getControlPageApiMethods ||
!controlPageApi?.getControlView ||
!controlPageApi?.getControlPageContract ||
@@ -94,6 +100,39 @@ TOOL_TABLE = browser-tool.tbl
"control page browser API namespace methods",
);
const controlPageApiSchema = controlPageApi.getControlPageApiSchema();
assertEqual(
controlPageApiSchema.manifestVersion,
1,
"control page API schema manifest version",
);
assertEqual(
controlPageApiSchema.fields.includes("readonlyStatusApiManifest"),
true,
"control page API schema readonly status field",
);
const controlPageApiManifest = controlPageApi.getControlPageApiManifest();
assertEqual(
controlPageApi.isSupportedControlPageApiManifest(controlPageApiManifest),
true,
"control page API supports manifest",
);
assertEqual(
controlPageApi.isSupportedControlPageApiManifest({ ...controlPageApiManifest, apiName: "other" }),
false,
"control page API rejects unsupported manifest name",
);
assertEqual(
controlPageApiManifest.methods.join(","),
expectedControlPageApiMethods.join(","),
"control page API manifest methods",
);
assertEqual(
controlPageApiManifest.readonlyStatusApiManifest.apiName,
"machine-session-readonly-status",
"control page API manifest readonly status API name",
);
const controlPageContract = controlPageApi.getControlPageContract();
assertEqual(
controlPageContract.methodNames.join(","),
@@ -179,6 +218,11 @@ TOOL_TABLE = browser-tool.tbl
"load-session",
"control page readonly status workflow last action after load",
);
assertEqual(
loadedStatus.sessionReadiness.ready,
true,
"control page readonly status session readiness",
);
assertEqual(
loadedStatus.controlView.status.lastAction,
"load-session",

View File

@@ -11,6 +11,7 @@
createControlPageBrowserApiMethods,
createIniPanelLaunchApiManifest,
createIniPanelLauncherLinkViewModel,
createIniPanelShellIntegrationManifest,
createIniPanelShellViewModel,
getIniPanelEntryByHref,
getVisibleIniPanelEntries,
@@ -22,9 +23,9 @@
}
}
async function loadLaunchFrame() {
async function loadFrame(src) {
const frame = document.createElement("iframe");
frame.src = "../../runtime/ui/ini-panel/launch.html";
frame.src = src;
document.body.appendChild(frame);
await new Promise((resolve, reject) => {
frame.addEventListener("load", resolve, { once: true });
@@ -33,11 +34,15 @@
return frame.contentDocument;
}
async function loadLaunchFrame() {
return loadFrame("../../runtime/ui/ini-panel/launch.html");
}
try {
const visibleEntries = getVisibleIniPanelEntries();
assertEqual(visibleEntries.length, 2, "launch visible entry length");
assertEqual(visibleEntries.length, 3, "launch visible entry length");
const launcherLinks = createIniPanelLauncherLinkViewModel();
assertEqual(launcherLinks.length, 2, "launcher link model length");
assertEqual(launcherLinks.length, 3, "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");
@@ -47,6 +52,11 @@
"Open read-only control view",
"launch manifest control entry",
);
assertEqual(
getIniPanelEntryByHref("./workflow-overview.html").title,
"Open shell handoff overview",
"launch manifest workflow entry",
);
assertEqual(
createIniPanelShellViewModel().controlPage.browserApiMethods.join(","),
createControlPageBrowserApiMethods().join(","),
@@ -69,6 +79,47 @@
!launchApi?.isSupportedLaunchApiManifest ||
!launchApi?.getLaunchApiSchema ||
!launchApi?.getLaunchApiManifest ||
!launchApi?.getShellIntegrationSchema ||
!launchApi?.getShellIntegrationManifest ||
!launchApi?.getShellIntegrationReadiness ||
!launchApi?.getShellSessionHandoffSummarySchema ||
!launchApi?.getShellSessionHandoffSummary ||
!launchApi?.isSupportedShellSessionHandoffSummary ||
!launchApi?.getShellSessionHandoffCompatibilityReport ||
!launchApi?.getShellSessionHandoffDisplayViewModel ||
!launchApi?.getShellSessionHandoffSnapshot ||
!launchApi?.getShellSessionHandoffRenderState ||
!launchApi?.getShellSessionHandoffActionPlan ||
!launchApi?.getShellSessionHandoffChecklist ||
!launchApi?.getShellSessionHandoffPackageSchema ||
!launchApi?.getShellSessionHandoffPackage ||
!launchApi?.getShellSessionHandoffPackageReport ||
!launchApi?.getShellSessionHandoffPackageDisplayViewModel ||
!launchApi?.getShellSessionHandoffPackageRenderState ||
!launchApi?.getShellSessionHandoffPackageMountState ||
!launchApi?.getShellSessionHandoffPackageMountDisplayViewModel ||
!launchApi?.getShellSessionHandoffPackageMountRenderState ||
!launchApi?.getShellSessionReadinessWorkflowReport ||
!launchApi?.getShellApiSurfaceInventory ||
!launchApi?.getMachineSessionPersistenceSummary ||
!launchApi?.renderShellSessionHandoffMountState ||
!launchApi?.getWorkflowOverviewContract ||
!launchApi?.getWorkflowOverviewReadiness ||
!launchApi?.mountWorkflowOverviewEmbedding ||
!launchApi?.getWorkflowOverviewEmbeddingReport ||
!launchApi?.getWorkflowOverviewEmbeddingDisplayViewModel ||
!launchApi?.getWorkflowOverviewEmbeddingRenderState ||
!launchApi?.getWorkflowOverviewEmbeddingDomContract ||
!launchApi?.getWorkflowOverviewEmbeddingDomReadiness ||
!launchApi?.renderWorkflowOverviewEmbeddingState ||
!launchApi?.mountWorkflowOverviewEmbeddingState ||
!launchApi?.getWorkflowOverviewEmbeddingMountDisplayViewModel ||
!launchApi?.getWorkflowOverviewEmbeddingMountRenderState ||
!launchApi?.getWorkflowOverviewEmbeddingMountDomContract ||
!launchApi?.getWorkflowOverviewEmbeddingMountDomReadiness ||
!launchApi?.renderWorkflowOverviewEmbeddingMountState ||
!launchApi?.mountWorkflowOverviewEmbeddingMountState ||
!launchApi?.isSupportedShellSessionHandoffPackage ||
!launchApi?.getLauncherLinks ||
!launchApi?.getControlPageApiMethods
) {
@@ -108,12 +159,470 @@
);
assertEqual(
launchApiManifest.methods.join(","),
"isSupportedLaunchApiManifest,getLaunchApiSchema,getLaunchApiManifest,getLauncherLinks,getControlPageApiMethods",
createIniPanelLaunchApiManifest().methods.join(","),
"launch API manifest methods",
);
const shellIntegrationSchema = launchApi.getShellIntegrationSchema();
const shellIntegrationManifest = launchApi.getShellIntegrationManifest();
const shellIntegrationReadiness = launchApi.getShellIntegrationReadiness();
const shellSessionHandoffSummarySchema = launchApi.getShellSessionHandoffSummarySchema();
const shellSessionHandoffSummary = launchApi.getShellSessionHandoffSummary();
const shellSessionHandoffCompatibilityReport = launchApi.getShellSessionHandoffCompatibilityReport();
const shellSessionHandoffDisplayViewModel = launchApi.getShellSessionHandoffDisplayViewModel();
const shellSessionHandoffSnapshot = launchApi.getShellSessionHandoffSnapshot();
const shellSessionHandoffRenderState = launchApi.getShellSessionHandoffRenderState();
const shellSessionHandoffActionPlan = launchApi.getShellSessionHandoffActionPlan();
const shellSessionHandoffChecklist = launchApi.getShellSessionHandoffChecklist();
const shellSessionHandoffPackageSchema = launchApi.getShellSessionHandoffPackageSchema();
const shellSessionHandoffPackage = launchApi.getShellSessionHandoffPackage();
const shellSessionHandoffPackageReport = launchApi.getShellSessionHandoffPackageReport();
const shellSessionHandoffPackageDisplayViewModel = launchApi.getShellSessionHandoffPackageDisplayViewModel();
const shellSessionHandoffPackageRenderState = launchApi.getShellSessionHandoffPackageRenderState();
const shellSessionHandoffPackageMountState = launchApi.getShellSessionHandoffPackageMountState();
const shellSessionHandoffPackageMountDisplayViewModel = launchApi.getShellSessionHandoffPackageMountDisplayViewModel();
const shellSessionHandoffPackageMountRenderState = launchApi.getShellSessionHandoffPackageMountRenderState();
const shellSessionReadinessWorkflowReport = launchApi.getShellSessionReadinessWorkflowReport();
const shellApiSurfaceInventory = launchApi.getShellApiSurfaceInventory();
const launchPersistenceSummary = launchApi.getMachineSessionPersistenceSummary();
const workflowOverviewContract = launchApi.getWorkflowOverviewContract();
assertEqual(
shellIntegrationSchema.manifestVersion,
1,
"shell integration schema manifest version",
);
assertEqual(
shellIntegrationManifest.apiName,
"ini-panel-shell-integration",
"shell integration manifest API name",
);
assertEqual(
shellIntegrationManifest.compatibility.launchApiManifest,
true,
"shell integration launch compatibility",
);
assertEqual(
shellIntegrationManifest.compatibility.controlPageApiManifest,
true,
"shell integration control-page compatibility",
);
assertEqual(
shellIntegrationManifest.controlPageApiManifest.apiName,
"ini-panel-control-page",
"shell integration control-page manifest name",
);
assertEqual(
shellIntegrationReadiness.ready,
true,
"shell integration readiness",
);
assertEqual(
shellIntegrationReadiness.counts.persistenceCapabilities,
3,
"shell integration readiness capability count",
);
assertEqual(
shellIntegrationReadiness.missing.join(","),
"",
"shell integration readiness missing checks",
);
assertEqual(
shellSessionHandoffSummary.ready,
true,
"shell session handoff summary readiness",
);
assertEqual(
shellSessionHandoffSummarySchema.summaryVersion,
1,
"shell session handoff summary schema version",
);
assertEqual(
launchApi.isSupportedShellSessionHandoffSummary(shellSessionHandoffSummary),
true,
"shell session handoff summary supported",
);
assertEqual(
launchApi.isSupportedShellSessionHandoffSummary({ ...shellSessionHandoffSummary, summaryVersion: 2 }),
false,
"shell session handoff summary rejects version",
);
assertEqual(
shellSessionHandoffCompatibilityReport.compatible,
true,
"shell session handoff compatibility report",
);
assertEqual(
shellSessionHandoffCompatibilityReport.summarySupported,
true,
"shell session handoff summary support report",
);
assertEqual(
shellSessionHandoffCompatibilityReport.missingFields.join(","),
"",
"shell session handoff compatibility missing fields",
);
assertEqual(
shellSessionHandoffDisplayViewModel.statusText,
"Ready",
"shell session handoff display status",
);
assertEqual(
shellSessionHandoffDisplayViewModel.rows.length,
5,
"shell session handoff display rows",
);
assertEqual(
shellSessionHandoffSnapshot.snapshotVersion,
1,
"shell session handoff snapshot version",
);
assertEqual(
shellSessionHandoffSnapshot.ready,
true,
"shell session handoff snapshot readiness",
);
assertEqual(
shellSessionHandoffRenderState.renderStateVersion,
1,
"shell session handoff render-state version",
);
assertEqual(
shellSessionHandoffRenderState.ready,
true,
"shell session handoff render-state readiness",
);
assertEqual(
shellSessionHandoffRenderState.statusLine,
"Ready: No blocking reasons",
"shell session handoff render-state status",
);
assertEqual(
shellSessionHandoffActionPlan.planVersion,
1,
"shell session handoff action-plan version",
);
assertEqual(
shellSessionHandoffActionPlan.ready,
true,
"shell session handoff action-plan readiness",
);
assertEqual(
shellSessionHandoffActionPlan.nextSteps.map(({ id }) => id).join(","),
"open-control-page,read-readonly-status",
"shell session handoff action-plan next steps",
);
assertEqual(
shellSessionHandoffActionPlan.requiredMethods.readStatus,
"readControlPageStatus",
"shell session handoff action-plan read method",
);
assertEqual(
shellSessionHandoffChecklist.checklistVersion,
1,
"shell session handoff checklist version",
);
assertEqual(
shellSessionHandoffChecklist.ready,
true,
"shell session handoff checklist readiness",
);
assertEqual(
shellSessionHandoffChecklist.checks.map(({ status }) => status).join(","),
"pass,pass,pass,pass,pass",
"shell session handoff checklist checks",
);
assertEqual(
shellSessionHandoffChecklist.nextStepIds.join(","),
"open-control-page,read-readonly-status",
"shell session handoff checklist next step ids",
);
assertEqual(
shellSessionHandoffPackage.packageVersion,
1,
"shell session handoff package version",
);
assertEqual(
shellSessionHandoffPackageSchema.packageVersion,
1,
"shell session handoff package schema version",
);
assertEqual(
launchApi.isSupportedShellSessionHandoffPackage(shellSessionHandoffPackage),
true,
"shell session handoff package supported",
);
assertEqual(
launchApi.isSupportedShellSessionHandoffPackage({ ...shellSessionHandoffPackage, packageVersion: 2 }),
false,
"shell session handoff package rejects version",
);
assertEqual(
shellSessionHandoffPackageReport.ready,
true,
"shell session handoff package report readiness",
);
assertEqual(
shellSessionHandoffPackageReport.rows.length,
6,
"shell session handoff package report rows",
);
assertEqual(
shellSessionHandoffPackageReport.missingPackageFields.join(","),
"",
"shell session handoff package report missing fields",
);
assertEqual(
shellSessionHandoffPackageDisplayViewModel.statusText,
"Ready",
"shell session handoff package display status",
);
assertEqual(
shellSessionHandoffPackageDisplayViewModel.rows.length,
shellSessionHandoffPackageReport.rows.length,
"shell session handoff package display rows",
);
assertEqual(
shellSessionHandoffPackageRenderState.renderStateVersion,
1,
"shell session handoff package render-state version",
);
assertEqual(
shellSessionHandoffPackageRenderState.ready,
true,
"shell session handoff package render-state readiness",
);
assertEqual(
shellSessionHandoffPackageRenderState.rows.length,
shellSessionHandoffPackageDisplayViewModel.rows.length,
"shell session handoff package render-state rows",
);
assertEqual(
shellSessionHandoffPackageMountState.mountStateVersion,
1,
"shell session handoff package mount-state version",
);
assertEqual(
shellSessionHandoffPackageMountState.packageReady,
true,
"shell session handoff package mount-state package readiness",
);
assertEqual(
shellSessionHandoffPackageMountState.targetPage.href,
"./control-page.html",
"shell session handoff package mount-state target",
);
assertEqual(
shellSessionHandoffPackageMountState.requiredMethods.readStatus,
"readControlPageStatus",
"shell session handoff package mount-state read method",
);
assertEqual(
shellSessionHandoffPackageMountState.sessionLoadState.phase,
"waiting-panel",
"shell session handoff package mount-state default session",
);
assertEqual(
shellSessionHandoffPackageMountDisplayViewModel.detailText,
"session-load-state",
"shell session handoff package mount display detail",
);
assertEqual(
shellSessionHandoffPackageMountRenderState.statusLine,
"Blocked: session-load-state",
"shell session handoff package mount render-state status",
);
assertEqual(
shellSessionReadinessWorkflowReport.apiName,
"ini-panel-shell-session-readiness-workflow-report",
"shell session readiness workflow report API",
);
assertEqual(
shellSessionReadinessWorkflowReport.rows.length,
3,
"shell session readiness workflow report rows",
);
assertEqual(
shellApiSurfaceInventory.apiName,
"ini-panel-shell-api-surface-inventory",
"shell API surface inventory API",
);
assertEqual(
shellApiSurfaceInventory.counts.sessionReadinessRows,
3,
"shell API surface inventory session readiness rows",
);
assertEqual(
shellApiSurfaceInventory.counts.persistenceSummaryRows,
7,
"shell API surface inventory persistence summary rows",
);
assertEqual(
launchPersistenceSummary.apiName,
"machine-session-persistence-summary",
"launch API persistence summary API",
);
assertEqual(
launchPersistenceSummary.rows.length,
7,
"launch API persistence summary rows",
);
assertEqual(
workflowOverviewContract.pageHref,
"./workflow-overview.html",
"workflow overview contract page",
);
assertEqual(
workflowOverviewContract.apiName,
"linuxCncIniPanelWorkflowOverviewApi",
"workflow overview contract API name",
);
assertEqual(
workflowOverviewContract.methodNames.includes("mountWorkflowOverview"),
true,
"workflow overview contract mount method",
);
assertEqual(
shellSessionHandoffPackage.ready,
true,
"shell session handoff package readiness",
);
assertEqual(
shellSessionHandoffPackage.manifests.launchMethods.includes("getShellSessionHandoffPackage"),
true,
"shell session handoff package launch methods",
);
assertEqual(
shellSessionHandoffPackage.manifests.controlPageMethods.includes("readControlPageStatus"),
true,
"shell session handoff package control methods",
);
assertEqual(
JSON.stringify(shellSessionHandoffSnapshot.readiness),
JSON.stringify(shellIntegrationReadiness),
"shell session handoff snapshot readiness object",
);
assertEqual(
JSON.stringify(shellSessionHandoffSnapshot.summary),
JSON.stringify(shellSessionHandoffSummary),
"shell session handoff snapshot summary object",
);
assertEqual(
JSON.stringify(shellSessionHandoffSnapshot.displayViewModel),
JSON.stringify(shellSessionHandoffDisplayViewModel),
"shell session handoff snapshot display object",
);
assertEqual(
JSON.stringify(shellSessionHandoffRenderState.rows),
JSON.stringify(shellSessionHandoffDisplayViewModel.rows),
"shell session handoff render-state rows",
);
assertEqual(
JSON.stringify(shellSessionHandoffActionPlan.displayRows),
JSON.stringify(shellSessionHandoffRenderState.rows),
"shell session handoff action-plan rows",
);
assertEqual(
JSON.stringify(shellSessionHandoffChecklist.displayRows),
JSON.stringify(shellSessionHandoffRenderState.rows),
"shell session handoff checklist rows",
);
assertEqual(
JSON.stringify(shellSessionHandoffPackage.checklist),
JSON.stringify(shellSessionHandoffChecklist),
"shell session handoff package checklist",
);
assertEqual(
launchDoc.querySelector("[data-handoff-status]").textContent,
"Ready: No blocking reasons",
"shell session handoff DOM status",
);
assertEqual(
launchDoc.querySelector("[data-handoff-shell]").dataset.handoffPhase,
"ready",
"shell session handoff DOM phase",
);
assertEqual(
launchDoc.querySelector("[data-handoff-shell]").dataset.handoffReady,
"true",
"shell session handoff DOM ready",
);
assertEqual(
launchDoc.querySelector("[data-handoff-shell]").dataset.handoffScope,
"package",
"shell session handoff DOM scope",
);
assertEqual(
launchDoc.querySelector('[data-handoff-value="package-supported"]').textContent,
"supported",
"shell session handoff DOM package support",
);
assertEqual(
launchDoc.querySelector('[data-handoff-value="package-ready"]').textContent,
"ready",
"shell session handoff DOM package readiness",
);
assertEqual(
launchDoc.querySelector('[data-handoff-value="missing-package-fields"]').textContent,
"none",
"shell session handoff DOM missing package fields",
);
assertEqual(
launchDoc.querySelector('[data-handoff-value="blocking-reasons"]').textContent,
"none",
"shell session handoff DOM blocking reasons",
);
assertEqual(
launchDoc.querySelector("[data-handoff-mount-status]").textContent,
"Blocked: session-load-state",
"shell session handoff mount DOM status",
);
assertEqual(
launchDoc.querySelector("[data-handoff-mount]").dataset.handoffPhase,
"blocked",
"shell session handoff mount DOM phase",
);
assertEqual(
launchDoc.querySelector("[data-handoff-mount]").dataset.handoffReady,
"false",
"shell session handoff mount DOM ready",
);
assertEqual(
launchDoc.querySelector("[data-handoff-mount]").dataset.handoffScope,
"mount",
"shell session handoff mount DOM scope",
);
assertEqual(
launchDoc.querySelector('[data-handoff-mount-value="session-load-state"]').textContent,
"waiting-panel",
"shell session handoff mount DOM session state",
);
assertEqual(
launchDoc.querySelector('[data-handoff-mount-value="session-load-state"]').dataset.handoffMountStatus,
"blocked",
"shell session handoff mount DOM session status",
);
assertEqual(
shellSessionHandoffSummary.targetPage.href,
"./control-page.html",
"shell session handoff summary target",
);
assertEqual(
shellSessionHandoffSummary.readonlyStatusFields.includes("session"),
true,
"shell session handoff summary readonly fields",
);
assertEqual(
shellSessionHandoffSummary.persistenceCapabilities.length,
3,
"shell session handoff summary persistence capabilities",
);
assertEqual(
JSON.stringify(shellIntegrationManifest),
JSON.stringify(createIniPanelShellIntegrationManifest()),
"shell integration manifest",
);
assertEqual(
launchApiManifest.visibleEntries.length,
2,
3,
"launch API manifest visible entry count",
);
assertEqual(
@@ -121,6 +630,11 @@
"./control-page.html",
"launch API manifest control target",
);
assertEqual(
launchApiManifest.targetPages[2].href,
"./workflow-overview.html",
"launch API manifest workflow target",
);
assertEqual(
launchApiManifest.persistenceCapabilities.length,
3,
@@ -146,9 +660,14 @@
"Open read-only control view",
"launch panel control link",
);
assertEqual(
launchDoc.querySelector('a[href="./workflow-overview.html"]').textContent,
"Open shell handoff overview",
"launch panel workflow link",
);
assertEqual(
launchApi.getLauncherLinks().length,
2,
3,
"launch API launcher link count",
);
assertEqual(
@@ -156,6 +675,78 @@
createControlPageBrowserApiMethods().join(","),
"launch API control-page browser methods",
);
const workflowOverviewDoc = await loadFrame("../../runtime/ui/ini-panel/workflow-overview.html");
const workflowOverviewApi = workflowOverviewDoc.defaultView.linuxCncIniPanelWorkflowOverviewApi;
if (
!workflowOverviewApi?.getWorkflowOverviewRenderState ||
!workflowOverviewApi?.getWorkflowOverviewContract ||
!workflowOverviewApi?.getWorkflowOverviewReadinessFromApi ||
!workflowOverviewApi?.mountWorkflowOverviewEmbedding ||
!workflowOverviewApi?.getWorkflowOverviewEmbeddingReport ||
!workflowOverviewApi?.getWorkflowOverviewEmbeddingDisplayViewModel ||
!workflowOverviewApi?.getWorkflowOverviewEmbeddingRenderState ||
!workflowOverviewApi?.getWorkflowOverviewEmbeddingDomContract ||
!workflowOverviewApi?.getWorkflowOverviewEmbeddingDomReadiness ||
!workflowOverviewApi?.renderWorkflowOverviewEmbeddingState ||
!workflowOverviewApi?.mountWorkflowOverviewEmbeddingState ||
!workflowOverviewApi?.getWorkflowOverviewEmbeddingMountDisplayViewModel ||
!workflowOverviewApi?.getWorkflowOverviewEmbeddingMountRenderState ||
!workflowOverviewApi?.getWorkflowOverviewEmbeddingMountDomContract ||
!workflowOverviewApi?.getWorkflowOverviewEmbeddingMountDomReadiness ||
!workflowOverviewApi?.renderWorkflowOverviewEmbeddingMountState ||
!workflowOverviewApi?.mountWorkflowOverviewEmbeddingMountState
) {
throw new Error("workflow overview API namespace did not expose shell helpers");
}
const workflowOverviewReadiness = launchApi.getWorkflowOverviewReadiness({
overviewApi: workflowOverviewApi,
});
const workflowOverviewEmbedding = launchApi.mountWorkflowOverviewEmbedding({
overviewApi: workflowOverviewApi,
});
const workflowOverviewEmbeddingReport = launchApi.getWorkflowOverviewEmbeddingReport(
workflowOverviewEmbedding,
);
const workflowOverviewEmbeddingDisplay = launchApi.getWorkflowOverviewEmbeddingDisplayViewModel(
workflowOverviewEmbeddingReport,
);
const workflowOverviewEmbeddingRenderState = launchApi.getWorkflowOverviewEmbeddingRenderState(
workflowOverviewEmbeddingDisplay,
);
const workflowOverviewEmbeddingDomContract = launchApi.getWorkflowOverviewEmbeddingDomContract();
const workflowOverviewEmbeddingDomReadiness = launchApi.getWorkflowOverviewEmbeddingDomReadiness({
documentRef: workflowOverviewDoc,
contract: workflowOverviewEmbeddingDomContract,
});
assertEqual(workflowOverviewApi.getWorkflowOverviewContract().pageHref, "./workflow-overview.html", "workflow overview page contract");
assertEqual(workflowOverviewReadiness.ready, true, "workflow overview readiness");
assertEqual(workflowOverviewReadiness.missing.join(","), "", "workflow overview readiness missing");
assertEqual(workflowOverviewApi.getWorkflowOverviewReadinessFromApi().ready, true, "workflow overview self readiness");
assertEqual(workflowOverviewEmbedding.apiName, "ini-panel-shell-workflow-overview-embedding", "workflow overview embedding API name");
assertEqual(workflowOverviewEmbedding.pageHref, "./workflow-overview.html", "workflow overview embedding page");
assertEqual(workflowOverviewEmbedding.ready, false, "workflow overview embedding default readiness");
assertEqual(workflowOverviewEmbedding.renderState.dataset.handoffScope, "workflow", "workflow overview embedding scope");
assertEqual(workflowOverviewEmbedding.mountResult.renderResult.rowCount, 8, "workflow overview embedding rows");
assertEqual(workflowOverviewEmbedding.error, null, "workflow overview embedding error");
assertEqual(workflowOverviewEmbeddingReport.apiName, "ini-panel-shell-workflow-overview-embedding-report", "workflow overview embedding report API name");
assertEqual(workflowOverviewEmbeddingReport.ready, false, "workflow overview embedding report readiness");
assertEqual(workflowOverviewEmbeddingReport.missingMethods.join(","), "", "workflow overview embedding report missing methods");
assertEqual(workflowOverviewEmbeddingReport.renderRowCount, 8, "workflow overview embedding report row count");
assertEqual(workflowOverviewEmbeddingReport.rows.length, 6, "workflow overview embedding report rows");
assertEqual(workflowOverviewEmbeddingDisplay.title, "Workflow overview embedding", "workflow overview embedding display title");
assertEqual(workflowOverviewEmbeddingDisplay.statusText, "Blocked", "workflow overview embedding display status");
assertEqual(workflowOverviewEmbeddingDisplay.rows.length, 6, "workflow overview embedding display rows");
assertEqual(workflowOverviewEmbeddingRenderState.statusLine, "Blocked: embedding:blocked, mount:blocked", "workflow overview embedding render-state status");
assertEqual(workflowOverviewEmbeddingRenderState.dataset.handoffScope, "workflow-overview-embedding", "workflow overview embedding render-state scope");
assertEqual(workflowOverviewEmbeddingDomContract.selectors.mount, "[data-workflow-overview-embedding]", "workflow overview embedding DOM contract mount");
assertEqual(workflowOverviewEmbeddingDomContract.rowDatasetKeys.detail[0], "workflowOverviewEmbeddingValue", "workflow overview embedding DOM contract row key");
assertEqual(workflowOverviewEmbeddingDomReadiness.ready, false, "workflow overview embedding DOM readiness");
assertEqual(workflowOverviewEmbeddingDomReadiness.missing.join(","), "mount,status,rows", "workflow overview embedding DOM readiness missing");
assertEqual(
workflowOverviewApi.getWorkflowOverviewEmbeddingDomReadiness().ready,
false,
"workflow overview self embedding DOM readiness",
);
status.textContent = "browser_ini_launch_smoke=ok";
console.log("browser_ini_launch_smoke=ok");
} catch (error) {

File diff suppressed because it is too large Load Diff

View File

@@ -376,6 +376,9 @@ TOOL_TABLE = browser-tool.tbl
!uiApi?.getMachineSessionStateReport ||
!uiApi?.getMachineSessionStateReportExport ||
!uiApi?.getMachineSessionWorkflowStatus ||
!uiApi?.getMachineSessionPersistenceSummary ||
!uiApi?.getMachineSessionPersistenceDisplayViewModel ||
!uiApi?.getMachineSessionPersistenceRenderState ||
!uiApi?.renderMachineSessionControlView
) {
throw new Error("INI panel API namespace did not expose state getters");
@@ -813,6 +816,44 @@ TOOL_TABLE = browser-tool.tbl
2,
"UI machine session workflow status canonical event count",
);
const machineSessionPersistenceSummary = uiApi.getMachineSessionPersistenceSummary();
assertEqual(
machineSessionPersistenceSummary.apiName,
"machine-session-persistence-summary",
"UI machine session persistence summary API name",
);
assertEqual(
machineSessionPersistenceSummary.ready,
true,
"UI machine session persistence summary readiness",
);
assertEqual(
machineSessionPersistenceSummary.rows.find((row) => row.id === "machine-files")?.value,
"ini, parameters, tool-table, g-code",
"UI machine session persistence summary machine files",
);
assertEqual(
machineSessionPersistenceSummary.rows.find((row) => row.id === "session-load")?.value,
"loaded: /work/session-machine.ini",
"UI machine session persistence summary session load",
);
const machineSessionPersistenceDisplay = uiApi.getMachineSessionPersistenceDisplayViewModel();
assertEqual(
machineSessionPersistenceDisplay.statusText,
"Ready",
"UI machine session persistence display status",
);
const machineSessionPersistenceRenderState = uiApi.getMachineSessionPersistenceRenderState();
assertEqual(
machineSessionPersistenceRenderState.statusLine,
"Ready: No missing persistence requirements",
"UI machine session persistence render-state status",
);
assertEqual(
machineSessionPersistenceRenderState.dataset.persistenceScope,
"opfs-session",
"UI machine session persistence render-state scope",
);
const machineSessionStateBundle = uiApi.getMachineSessionStateBundle();
assertEqual(
machineSessionStateBundle.report.status.lastAction,

View File

@@ -0,0 +1,621 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>LinuxCNC INI Workflow Overview Smoke</title>
</head>
<body>
<pre id="status">running</pre>
<script type="module">
function assertEqual(actual, expected, label) {
if (actual !== expected) {
throw new Error(`${label}: expected ${expected}, got ${actual}`);
}
}
async function loadWorkflowOverviewFrame() {
const frame = document.createElement("iframe");
frame.src = "../../runtime/ui/ini-panel/workflow-overview.html";
document.body.appendChild(frame);
await new Promise((resolve, reject) => {
frame.addEventListener("load", resolve, { once: true });
frame.addEventListener("error", reject, { once: true });
});
return frame.contentDocument;
}
try {
const workflowDoc = await loadWorkflowOverviewFrame();
const workflowApi = workflowDoc.defaultView.linuxCncIniPanelWorkflowOverviewApi;
if (
!workflowApi?.getWorkflowOverviewRenderState ||
!workflowApi?.getWorkflowOverviewDisplayViewModel ||
!workflowApi?.getWorkflowOverviewSummary ||
!workflowApi?.getWorkflowOverviewSessionReadinessReport ||
!workflowApi?.getWorkflowOverviewApiSurfaceInventory ||
!workflowApi?.getWorkflowOverviewPersistenceSummary ||
!workflowApi?.validateWorkflowOverviewReleaseReadinessArtifactJson ||
!workflowApi?.runWorkflowOverviewReleaseReadinessArtifactJsonWorkflow ||
!workflowApi?.getWorkflowOverviewReleaseReadinessArtifactJsonWorkflowSummaryViewModel ||
!workflowApi?.getWorkflowOverviewReleaseReadinessArtifactJsonWorkflowActionPlan ||
!workflowApi?.getWorkflowOverviewReleaseReadinessArtifactJsonWorkflowRenderState ||
!workflowApi?.getWorkflowOverviewReleaseReadinessArtifactJsonWorkflowDomContract ||
!workflowApi?.getWorkflowOverviewReleaseReadinessArtifactJsonWorkflowDomReadiness ||
!workflowApi?.renderWorkflowOverviewReleaseReadinessArtifactJsonWorkflowState ||
!workflowApi?.mountWorkflowOverviewReleaseReadinessArtifactJsonWorkflowState ||
!workflowApi?.loadWorkflowOverviewReleaseReadinessArtifactUrl ||
!workflowApi?.getWorkflowOverviewReleaseReadinessArtifactUrlWorkflowSummaryViewModel ||
!workflowApi?.getWorkflowOverviewReleaseReadinessArtifactUrlWorkflowActionPlan ||
!workflowApi?.getWorkflowOverviewReleaseReadinessArtifactUrlWorkflowRenderState ||
!workflowApi?.getWorkflowOverviewReleaseReadinessArtifactUrlWorkflowDomContract ||
!workflowApi?.getWorkflowOverviewReleaseReadinessArtifactUrlWorkflowDomReadiness ||
!workflowApi?.renderWorkflowOverviewReleaseReadinessArtifactUrlWorkflowState ||
!workflowApi?.mountWorkflowOverviewReleaseReadinessArtifactUrlWorkflowState ||
!workflowApi?.getWorkflowOverviewReleaseReadinessArtifactValidationSummaryViewModel ||
!workflowApi?.getWorkflowOverviewReleaseReadinessArtifactValidationActionPlan ||
!workflowApi?.getWorkflowOverviewReleaseReadinessArtifactValidationActionPlanRenderState ||
!workflowApi?.getWorkflowOverviewReleaseReadinessArtifactValidationActionPlanDomContract ||
!workflowApi?.getWorkflowOverviewReleaseReadinessArtifactValidationActionPlanDomReadiness ||
!workflowApi?.renderWorkflowOverviewReleaseReadinessArtifactValidationActionPlanState ||
!workflowApi?.mountWorkflowOverviewReleaseReadinessArtifactValidationActionPlanState ||
!workflowApi?.getWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryViewModel ||
!workflowApi?.getWorkflowOverviewReleaseReadinessArtifactUrlGateExecutionSummaryViewModel ||
!workflowApi?.getWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryRenderState ||
!workflowApi?.getWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryDomContract ||
!workflowApi?.getWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryDomReadiness ||
!workflowApi?.renderWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryState ||
!workflowApi?.mountWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryState ||
!workflowApi?.getWorkflowOverviewReleaseReadinessArtifactDisplayViewModel ||
!workflowApi?.getWorkflowOverviewReleaseReadinessArtifactRenderState ||
!workflowApi?.getWorkflowOverviewReleaseReadinessArtifactDomContract ||
!workflowApi?.getWorkflowOverviewReleaseReadinessArtifactDomReadiness ||
!workflowApi?.renderWorkflowOverviewReleaseReadinessArtifactState ||
!workflowApi?.mountWorkflowOverviewReleaseReadinessArtifactState ||
!workflowApi?.getWorkflowOverviewDomContract ||
!workflowApi?.getWorkflowOverviewDomReadiness ||
!workflowApi?.renderWorkflowOverview ||
!workflowApi?.mountWorkflowOverview ||
!workflowApi?.createWorkflowOverviewRenderState ||
!workflowApi?.getWorkflowOverviewEmbeddingReport ||
!workflowApi?.getWorkflowOverviewEmbeddingDisplayViewModel ||
!workflowApi?.getWorkflowOverviewEmbeddingRenderState ||
!workflowApi?.getWorkflowOverviewEmbeddingDomContract ||
!workflowApi?.getWorkflowOverviewEmbeddingDomReadiness ||
!workflowApi?.renderWorkflowOverviewEmbeddingState ||
!workflowApi?.mountWorkflowOverviewEmbeddingState ||
!workflowApi?.getWorkflowOverviewEmbeddingMountDisplayViewModel ||
!workflowApi?.getWorkflowOverviewEmbeddingMountRenderState ||
!workflowApi?.getWorkflowOverviewEmbeddingMountDomContract ||
!workflowApi?.getWorkflowOverviewEmbeddingMountDomReadiness ||
!workflowApi?.renderWorkflowOverviewEmbeddingMountState ||
!workflowApi?.mountWorkflowOverviewEmbeddingMountState
) {
throw new Error("workflow overview API namespace did not expose overview helpers");
}
const renderState = workflowApi.getWorkflowOverviewRenderState();
const displayViewModel = workflowApi.getWorkflowOverviewDisplayViewModel();
const summary = workflowApi.getWorkflowOverviewSummary();
const sessionReadinessReport = workflowApi.getWorkflowOverviewSessionReadinessReport();
const apiSurfaceInventory = workflowApi.getWorkflowOverviewApiSurfaceInventory();
const persistenceSummary = workflowApi.getWorkflowOverviewPersistenceSummary();
const releaseArtifactJson = await fetch("../../tests/fixtures/project-release-readiness-ready.json")
.then((response) => response.text());
const releaseArtifactValidation = workflowApi.validateWorkflowOverviewReleaseReadinessArtifactJson(
releaseArtifactJson,
);
const releaseArtifactJsonWorkflow = workflowApi.runWorkflowOverviewReleaseReadinessArtifactJsonWorkflow({
artifactJson: releaseArtifactJson,
});
const releaseArtifactJsonWorkflowSummary = workflowApi
.getWorkflowOverviewReleaseReadinessArtifactJsonWorkflowSummaryViewModel(releaseArtifactJsonWorkflow);
const releaseArtifactJsonWorkflowActionPlan = workflowApi
.getWorkflowOverviewReleaseReadinessArtifactJsonWorkflowActionPlan(releaseArtifactJsonWorkflow);
const releaseArtifactJsonWorkflowRenderState = workflowApi
.getWorkflowOverviewReleaseReadinessArtifactJsonWorkflowRenderState(releaseArtifactJsonWorkflowSummary);
const releaseArtifactValidationSummary = workflowApi
.getWorkflowOverviewReleaseReadinessArtifactValidationSummaryViewModel(releaseArtifactValidation);
const releaseArtifactValidationActionPlan = workflowApi
.getWorkflowOverviewReleaseReadinessArtifactValidationActionPlan(releaseArtifactValidation);
const releaseArtifactValidationActionPlanRenderState = workflowApi
.getWorkflowOverviewReleaseReadinessArtifactValidationActionPlanRenderState(
releaseArtifactValidationActionPlan,
);
const releaseArtifactDisplay = workflowApi.getWorkflowOverviewReleaseReadinessArtifactDisplayViewModel(
releaseArtifactValidation,
);
const releaseArtifactRenderState = workflowApi.getWorkflowOverviewReleaseReadinessArtifactRenderState(
releaseArtifactDisplay,
);
const releaseArtifactDomContract = workflowApi.getWorkflowOverviewReleaseReadinessArtifactDomContract();
const releaseArtifactDomReadinessMissing = workflowApi.getWorkflowOverviewReleaseReadinessArtifactDomReadiness();
const releaseArtifactJsonWorkflowDomReadinessMissing = workflowApi
.getWorkflowOverviewReleaseReadinessArtifactJsonWorkflowDomReadiness();
const domContract = workflowApi.getWorkflowOverviewDomContract();
const domReadiness = workflowApi.getWorkflowOverviewDomReadiness();
const renderResult = workflowApi.renderWorkflowOverview();
const mountResult = workflowApi.mountWorkflowOverview();
const embeddingReport = workflowApi.getWorkflowOverviewEmbeddingReport({
phase: mountResult.phase,
ready: mountResult.ready,
pageHref: "./workflow-overview.html",
readiness: {
ready: true,
missing: [],
},
renderState,
mountResult,
error: null,
});
const embeddingDisplay = workflowApi.getWorkflowOverviewEmbeddingDisplayViewModel(embeddingReport);
const embeddingRenderState = workflowApi.getWorkflowOverviewEmbeddingRenderState(embeddingDisplay);
const embeddingDomContract = workflowApi.getWorkflowOverviewEmbeddingDomContract();
const embeddingDomReadiness = workflowApi.getWorkflowOverviewEmbeddingDomReadiness();
assertEqual(summary.summaryVersion, 1, "workflow overview summary version");
assertEqual(
sessionReadinessReport.apiName,
"ini-panel-shell-session-readiness-workflow-report",
"workflow overview session readiness report API name",
);
assertEqual(sessionReadinessReport.rows.length, 3, "workflow overview session readiness report rows");
assertEqual(
apiSurfaceInventory.apiName,
"ini-panel-shell-api-surface-inventory",
"workflow overview API surface inventory API name",
);
assertEqual(apiSurfaceInventory.rows.length, 6, "workflow overview API surface inventory rows");
assertEqual(
apiSurfaceInventory.counts.persistenceSummaryRows,
7,
"workflow overview API surface inventory persistence summary rows",
);
assertEqual(
persistenceSummary.apiName,
"machine-session-persistence-summary",
"workflow overview persistence summary API name",
);
assertEqual(persistenceSummary.rows.length, 7, "workflow overview persistence summary rows");
assertEqual(
releaseArtifactValidation.apiName,
"ini-panel-shell-workflow-overview-release-readiness-artifact-validation",
"workflow overview release artifact validation API name",
);
assertEqual(releaseArtifactValidation.ready, true, "workflow overview release artifact validation readiness");
assertEqual(releaseArtifactValidation.gateCount, 12, "workflow overview release artifact validation gates");
assertEqual(
releaseArtifactValidationSummary.statusLine,
"Ready: Artifact evidence complete",
"workflow overview release artifact validation summary",
);
assertEqual(releaseArtifactDisplay.statusText, "Ready", "workflow overview release artifact display status");
assertEqual(releaseArtifactRenderState.statusLine, "Ready: No blocking reasons", "workflow overview release artifact render status");
assertEqual(
releaseArtifactRenderState.dataset.handoffScope,
"workflow-overview-release-readiness-artifact",
"workflow overview release artifact render scope",
);
assertEqual(
releaseArtifactDomContract.selectors.mount,
"[data-workflow-overview-release-readiness-artifact]",
"workflow overview release artifact DOM mount selector",
);
assertEqual(releaseArtifactDomReadinessMissing.ready, false, "workflow overview release artifact missing DOM readiness");
assertEqual(displayViewModel.title, "Shell handoff workflow", "workflow overview display title");
assertEqual(renderState.renderStateVersion, 1, "workflow overview render-state version");
assertEqual(renderState.dataset.handoffScope, "workflow", "workflow overview render-state scope");
assertEqual(domContract.selectors.mount, "[data-handoff-workflow]", "workflow overview DOM contract mount selector");
assertEqual(domReadiness.ready, true, "workflow overview DOM readiness");
assertEqual(renderResult.rendered, true, "workflow overview render result");
assertEqual(renderResult.rowCount, 8, "workflow overview render row count");
assertEqual(mountResult.ready, false, "workflow overview mount result readiness");
assertEqual(mountResult.renderResult.rendered, true, "workflow overview mount result rendered");
assertEqual(embeddingReport.apiName, "ini-panel-shell-workflow-overview-embedding-report", "workflow overview embedding report API name");
assertEqual(embeddingReport.ready, false, "workflow overview embedding report readiness");
assertEqual(embeddingReport.renderRowCount, 8, "workflow overview embedding report row count");
assertEqual(embeddingReport.rows.length, 6, "workflow overview embedding report rows");
assertEqual(embeddingDisplay.title, "Workflow overview embedding", "workflow overview embedding display title");
assertEqual(embeddingDisplay.statusText, "Blocked", "workflow overview embedding display status");
assertEqual(embeddingDisplay.rows.length, 6, "workflow overview embedding display rows");
assertEqual(embeddingRenderState.statusLine, "Blocked: embedding:blocked, mount:blocked", "workflow overview embedding render-state status");
assertEqual(embeddingRenderState.dataset.handoffScope, "workflow-overview-embedding", "workflow overview embedding render-state scope");
assertEqual(embeddingDomContract.selectors.status, "[data-workflow-overview-embedding-status]", "workflow overview embedding DOM contract status selector");
assertEqual(embeddingDomContract.renderResultFields.includes("rowIds"), true, "workflow overview embedding DOM contract render fields");
assertEqual(embeddingDomReadiness.ready, false, "workflow overview embedding DOM readiness");
assertEqual(embeddingDomReadiness.missing.join(","), "mount,status,rows", "workflow overview embedding DOM readiness missing");
const embeddingMount = workflowDoc.createElement("section");
embeddingMount.dataset.workflowOverviewEmbedding = "";
const embeddingStatus = workflowDoc.createElement("p");
embeddingStatus.dataset.workflowOverviewEmbeddingStatus = "";
const embeddingRows = workflowDoc.createElement("dl");
embeddingRows.dataset.workflowOverviewEmbeddingRows = "";
embeddingMount.append(embeddingStatus, embeddingRows);
workflowDoc.body.append(embeddingMount);
const embeddingRenderResult = workflowApi.renderWorkflowOverviewEmbeddingState(embeddingRenderState);
const embeddingMountState = workflowApi.mountWorkflowOverviewEmbeddingState({
renderState: embeddingRenderState,
});
const embeddingMountDisplay = workflowApi.getWorkflowOverviewEmbeddingMountDisplayViewModel(
embeddingMountState,
);
const embeddingMountRenderState = workflowApi.getWorkflowOverviewEmbeddingMountRenderState(
embeddingMountDisplay,
);
const embeddingMountDomContract = workflowApi.getWorkflowOverviewEmbeddingMountDomContract();
const embeddingMountDomReadinessMissing = workflowApi.getWorkflowOverviewEmbeddingMountDomReadiness();
assertEqual(embeddingRenderResult.rendered, true, "workflow overview embedding render result");
assertEqual(embeddingRenderResult.rowCount, 6, "workflow overview embedding render row count");
assertEqual(embeddingRenderResult.dataset.handoffScope, "workflow-overview-embedding", "workflow overview embedding render scope");
assertEqual(embeddingStatus.textContent, embeddingRenderState.statusLine, "workflow overview embedding rendered status");
assertEqual(embeddingMountState.apiName, "ini-panel-shell-workflow-overview-embedding-dom-mount", "workflow overview embedding mount API");
assertEqual(embeddingMountState.ready, false, "workflow overview embedding mount readiness");
assertEqual(embeddingMountState.renderResult.rowCount, 6, "workflow overview embedding mount row count");
assertEqual(embeddingMountDisplay.statusText, "Blocked", "workflow overview embedding mount display status");
assertEqual(embeddingMountDisplay.rows.length, 7, "workflow overview embedding mount display rows");
assertEqual(embeddingMountRenderState.statusLine, "Blocked: mount:blocked", "workflow overview embedding mount render status");
assertEqual(embeddingMountRenderState.rows.length, 7, "workflow overview embedding mount render rows");
assertEqual(embeddingMountDomContract.selectors.mount, "[data-workflow-overview-embedding-mount]", "workflow overview embedding mount DOM selector");
assertEqual(embeddingMountDomContract.rowDatasetKeys.term[0], "workflowOverviewEmbeddingMountRow", "workflow overview embedding mount row key");
assertEqual(embeddingMountDomReadinessMissing.ready, false, "workflow overview embedding mount missing DOM readiness");
assertEqual(embeddingMountDomReadinessMissing.missing.join(","), "mount,status,rows", "workflow overview embedding mount missing DOM nodes");
const embeddingMountStateNode = workflowDoc.createElement("section");
embeddingMountStateNode.dataset.workflowOverviewEmbeddingMount = "";
const embeddingMountStatus = workflowDoc.createElement("p");
embeddingMountStatus.dataset.workflowOverviewEmbeddingMountStatus = "";
const embeddingMountRows = workflowDoc.createElement("dl");
embeddingMountRows.dataset.workflowOverviewEmbeddingMountRows = "";
embeddingMountStateNode.append(embeddingMountStatus, embeddingMountRows);
workflowDoc.body.append(embeddingMountStateNode);
const embeddingMountDomReadinessReady = workflowApi.getWorkflowOverviewEmbeddingMountDomReadiness();
const embeddingMountRenderResult = workflowApi.renderWorkflowOverviewEmbeddingMountState(
embeddingMountRenderState,
);
const embeddingMountDomMount = workflowApi.mountWorkflowOverviewEmbeddingMountState({
renderState: embeddingMountRenderState,
});
assertEqual(embeddingMountDomReadinessReady.ready, true, "workflow overview embedding mount ready DOM readiness");
assertEqual(embeddingMountDomReadinessReady.missing.join(","), "", "workflow overview embedding mount ready DOM missing");
assertEqual(embeddingMountRenderResult.rendered, true, "workflow overview embedding mount render result");
assertEqual(embeddingMountRenderResult.rowCount, 7, "workflow overview embedding mount render row count");
assertEqual(embeddingMountRenderResult.dataset.handoffScope, "workflow-overview-embedding-mount", "workflow overview embedding mount render scope");
assertEqual(embeddingMountDomMount.apiName, "ini-panel-shell-workflow-overview-embedding-mount-dom-mount", "workflow overview embedding mount DOM mount API");
assertEqual(embeddingMountDomMount.ready, false, "workflow overview embedding mount DOM mount readiness");
assertEqual(embeddingMountDomMount.renderResult.rowCount, 7, "workflow overview embedding mount DOM mount row count");
assertEqual(embeddingMountDomMount.error, null, "workflow overview embedding mount DOM mount error");
assertEqual(embeddingMountStatus.textContent, embeddingMountRenderState.statusLine, "workflow overview embedding mount rendered status");
const releaseArtifactNode = workflowDoc.createElement("section");
releaseArtifactNode.dataset.workflowOverviewReleaseReadinessArtifact = "";
const releaseArtifactStatus = workflowDoc.createElement("p");
releaseArtifactStatus.dataset.workflowOverviewReleaseReadinessArtifactStatus = "";
const releaseArtifactRows = workflowDoc.createElement("dl");
releaseArtifactRows.dataset.workflowOverviewReleaseReadinessArtifactRows = "";
releaseArtifactNode.append(releaseArtifactStatus, releaseArtifactRows);
workflowDoc.body.append(releaseArtifactNode);
const releaseArtifactDomReadinessReady = workflowApi.getWorkflowOverviewReleaseReadinessArtifactDomReadiness();
const releaseArtifactRenderResult = workflowApi.renderWorkflowOverviewReleaseReadinessArtifactState(
releaseArtifactRenderState,
);
const releaseArtifactMountResult = workflowApi.mountWorkflowOverviewReleaseReadinessArtifactState({
renderState: releaseArtifactRenderState,
});
const releaseArtifactValidationActionPlanNode = workflowDoc.createElement("section");
releaseArtifactValidationActionPlanNode.dataset.workflowOverviewReleaseReadinessArtifactValidationActionPlan = "";
const releaseArtifactValidationActionPlanStatus = workflowDoc.createElement("p");
releaseArtifactValidationActionPlanStatus.dataset.workflowOverviewReleaseReadinessArtifactValidationActionPlanStatus = "";
const releaseArtifactValidationActionPlanRows = workflowDoc.createElement("dl");
releaseArtifactValidationActionPlanRows.dataset.workflowOverviewReleaseReadinessArtifactValidationActionPlanRows = "";
releaseArtifactValidationActionPlanNode.append(
releaseArtifactValidationActionPlanStatus,
releaseArtifactValidationActionPlanRows,
);
workflowDoc.body.append(releaseArtifactValidationActionPlanNode);
const releaseArtifactValidationActionPlanDomReadinessReady = workflowApi
.getWorkflowOverviewReleaseReadinessArtifactValidationActionPlanDomReadiness();
const releaseArtifactValidationActionPlanRenderResult = workflowApi
.renderWorkflowOverviewReleaseReadinessArtifactValidationActionPlanState(
releaseArtifactValidationActionPlanRenderState,
);
const releaseArtifactValidationActionPlanMountResult = workflowApi
.mountWorkflowOverviewReleaseReadinessArtifactValidationActionPlanState({
renderState: releaseArtifactValidationActionPlanRenderState,
});
const releaseArtifactJsonWorkflowNode = workflowDoc.createElement("section");
releaseArtifactJsonWorkflowNode.dataset.workflowOverviewReleaseReadinessArtifactJsonWorkflow = "";
const releaseArtifactJsonWorkflowStatus = workflowDoc.createElement("p");
releaseArtifactJsonWorkflowStatus.dataset.workflowOverviewReleaseReadinessArtifactJsonWorkflowStatus = "";
const releaseArtifactJsonWorkflowRows = workflowDoc.createElement("dl");
releaseArtifactJsonWorkflowRows.dataset.workflowOverviewReleaseReadinessArtifactJsonWorkflowRows = "";
releaseArtifactJsonWorkflowNode.append(releaseArtifactJsonWorkflowStatus, releaseArtifactJsonWorkflowRows);
workflowDoc.body.append(releaseArtifactJsonWorkflowNode);
const releaseArtifactJsonWorkflowDomReadinessReady = workflowApi
.getWorkflowOverviewReleaseReadinessArtifactJsonWorkflowDomReadiness();
const releaseArtifactJsonWorkflowRenderResult = workflowApi
.renderWorkflowOverviewReleaseReadinessArtifactJsonWorkflowState(
releaseArtifactJsonWorkflowRenderState,
);
const releaseArtifactJsonWorkflowMountResult = workflowApi
.mountWorkflowOverviewReleaseReadinessArtifactJsonWorkflowState({
renderState: releaseArtifactJsonWorkflowRenderState,
});
const releaseArtifactUrlWorkflow = await workflowApi.loadWorkflowOverviewReleaseReadinessArtifactUrl({
artifactUrl: "../../tests/fixtures/project-release-readiness-ready.json",
mount: true,
});
const releaseArtifactUrlWorkflowSummary = workflowApi
.getWorkflowOverviewReleaseReadinessArtifactUrlWorkflowSummaryViewModel(releaseArtifactUrlWorkflow);
const releaseArtifactUrlWorkflowActionPlan = workflowApi
.getWorkflowOverviewReleaseReadinessArtifactUrlWorkflowActionPlan(releaseArtifactUrlWorkflow);
const releaseArtifactUrlWorkflowRenderState = workflowApi
.getWorkflowOverviewReleaseReadinessArtifactUrlWorkflowRenderState(releaseArtifactUrlWorkflowSummary);
const releaseArtifactUrlWorkflowNode = workflowDoc.createElement("section");
releaseArtifactUrlWorkflowNode.dataset.workflowOverviewReleaseReadinessArtifactUrlWorkflow = "";
const releaseArtifactUrlWorkflowStatus = workflowDoc.createElement("p");
releaseArtifactUrlWorkflowStatus.dataset.workflowOverviewReleaseReadinessArtifactUrlWorkflowStatus = "";
const releaseArtifactUrlWorkflowRows = workflowDoc.createElement("dl");
releaseArtifactUrlWorkflowRows.dataset.workflowOverviewReleaseReadinessArtifactUrlWorkflowRows = "";
releaseArtifactUrlWorkflowNode.append(releaseArtifactUrlWorkflowStatus, releaseArtifactUrlWorkflowRows);
workflowDoc.body.append(releaseArtifactUrlWorkflowNode);
const releaseArtifactUrlWorkflowDomReadinessReady = workflowApi
.getWorkflowOverviewReleaseReadinessArtifactUrlWorkflowDomReadiness();
const releaseArtifactUrlWorkflowRenderResult = workflowApi
.renderWorkflowOverviewReleaseReadinessArtifactUrlWorkflowState(
releaseArtifactUrlWorkflowRenderState,
);
const releaseArtifactUrlWorkflowMountResult = workflowApi
.mountWorkflowOverviewReleaseReadinessArtifactUrlWorkflowState({
renderState: releaseArtifactUrlWorkflowRenderState,
});
const releaseArtifactGateExecutionSummary = workflowApi
.getWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryViewModel(releaseArtifactValidation);
const releaseArtifactGateExecutionSummaryRenderState = workflowApi
.getWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryRenderState(
releaseArtifactGateExecutionSummary,
);
const releaseArtifactGateExecutionSummaryNode = workflowDoc.createElement("section");
releaseArtifactGateExecutionSummaryNode.dataset.workflowOverviewReleaseReadinessArtifactGateExecutionSummary = "";
const releaseArtifactGateExecutionSummaryStatus = workflowDoc.createElement("p");
releaseArtifactGateExecutionSummaryStatus.dataset.workflowOverviewReleaseReadinessArtifactGateExecutionSummaryStatus = "";
const releaseArtifactGateExecutionSummaryRows = workflowDoc.createElement("dl");
releaseArtifactGateExecutionSummaryRows.dataset.workflowOverviewReleaseReadinessArtifactGateExecutionSummaryRows = "";
releaseArtifactGateExecutionSummaryNode.append(
releaseArtifactGateExecutionSummaryStatus,
releaseArtifactGateExecutionSummaryRows,
);
workflowDoc.body.append(releaseArtifactGateExecutionSummaryNode);
const releaseArtifactGateExecutionSummaryDomReadinessReady = workflowApi
.getWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryDomReadiness();
const releaseArtifactGateExecutionSummaryRenderResult = workflowApi
.renderWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryState(
releaseArtifactGateExecutionSummaryRenderState,
);
const releaseArtifactGateExecutionSummaryMountResult = workflowApi
.mountWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryState({
renderState: releaseArtifactGateExecutionSummaryRenderState,
});
const releaseArtifactUrlGateExecutionSummary = workflowApi
.getWorkflowOverviewReleaseReadinessArtifactUrlGateExecutionSummaryViewModel(releaseArtifactUrlWorkflow);
assertEqual(releaseArtifactDomReadinessReady.ready, true, "workflow overview release artifact DOM readiness");
assertEqual(releaseArtifactRenderResult.rendered, true, "workflow overview release artifact render result");
assertEqual(releaseArtifactRenderResult.rowCount, 8, "workflow overview release artifact render row count");
assertEqual(
releaseArtifactRenderResult.rowIds.includes("gate-execution-manifest"),
true,
"workflow overview release artifact execution manifest row",
);
assertEqual(
releaseArtifactRenderResult.rowIds.includes("gate-execution-summary"),
true,
"workflow overview release artifact execution summary row",
);
assertEqual(releaseArtifactMountResult.ready, true, "workflow overview release artifact mount readiness");
assertEqual(releaseArtifactMountResult.renderResult.rowCount, 8, "workflow overview release artifact mount rows");
assertEqual(
releaseArtifactJsonWorkflowDomReadinessMissing.missing.join(","),
"mount,status,rows",
"workflow overview release artifact JSON workflow missing DOM readiness",
);
assertEqual(
releaseArtifactJsonWorkflowDomReadinessReady.ready,
true,
"workflow overview release artifact JSON workflow DOM readiness",
);
assertEqual(
releaseArtifactJsonWorkflowRenderResult.rowCount,
5,
"workflow overview release artifact JSON workflow render rows",
);
assertEqual(
releaseArtifactJsonWorkflowRenderResult.rowIds.includes("gate-execution-summary"),
true,
"workflow overview release artifact JSON workflow gate row",
);
assertEqual(
releaseArtifactJsonWorkflowMountResult.ready,
true,
"workflow overview release artifact JSON workflow mount readiness",
);
assertEqual(
releaseArtifactJsonWorkflowStatus.textContent,
"Ready: Artifact JSON workflow complete",
"workflow overview release artifact JSON workflow rendered status",
);
assertEqual(releaseArtifactUrlWorkflow.ready, true, "workflow overview release artifact URL workflow readiness");
assertEqual(releaseArtifactUrlWorkflow.httpStatus, 200, "workflow overview release artifact URL workflow status");
assertEqual(
releaseArtifactUrlWorkflow.validationSummaryViewModel.statusLine,
"Ready: Artifact evidence complete",
"workflow overview release artifact URL workflow summary",
);
assertEqual(
releaseArtifactUrlWorkflowSummary.statusLine,
"Ready: Artifact URL workflow complete",
"workflow overview release artifact URL workflow summary view model",
);
assertEqual(
releaseArtifactUrlWorkflowRenderState.dataset.handoffScope,
"workflow-overview-release-readiness-artifact-url-workflow",
"workflow overview release artifact URL workflow render scope",
);
assertEqual(
releaseArtifactUrlWorkflowDomReadinessReady.ready,
true,
"workflow overview release artifact URL workflow DOM readiness",
);
assertEqual(
releaseArtifactUrlWorkflowRenderResult.rowCount,
8,
"workflow overview release artifact URL workflow render rows",
);
assertEqual(
releaseArtifactUrlWorkflowRenderResult.rowIds.includes("mount-result"),
true,
"workflow overview release artifact URL workflow mount row",
);
assertEqual(
releaseArtifactUrlWorkflowMountResult.ready,
true,
"workflow overview release artifact URL workflow DOM mount readiness",
);
assertEqual(
releaseArtifactUrlWorkflowStatus.textContent,
"Ready: Artifact URL workflow complete",
"workflow overview release artifact URL workflow rendered status",
);
assertEqual(
releaseArtifactJsonWorkflow.statusLine,
"Ready: Artifact JSON workflow complete",
"workflow overview release artifact JSON workflow",
);
assertEqual(
releaseArtifactJsonWorkflowSummary.statusLine,
"Ready: Artifact JSON workflow complete",
"workflow overview release artifact JSON workflow summary",
);
assertEqual(
releaseArtifactJsonWorkflowActionPlan.shellScript,
"# Project release readiness artifact validation is already ready.\n",
"workflow overview release artifact JSON workflow action plan",
);
assertEqual(
releaseArtifactJsonWorkflowRenderState.dataset.handoffScope,
"workflow-overview-release-readiness-artifact-json-workflow",
"workflow overview release artifact JSON workflow render state scope",
);
assertEqual(
releaseArtifactJsonWorkflow.gateExecutionSummaryViewModel.statusLine,
"Ready: All release gate evidence present",
"workflow overview release artifact JSON workflow gate summary",
);
assertEqual(
releaseArtifactJsonWorkflow.actionPlan.nextCommand,
null,
"workflow overview release artifact JSON workflow action plan",
);
assertEqual(
releaseArtifactUrlWorkflowActionPlan.shellScript,
"# Project release readiness artifact URL workflow is already ready.\n",
"workflow overview release artifact URL workflow action plan",
);
assertEqual(
releaseArtifactValidationActionPlan.shellScript,
"# Project release readiness artifact validation is already ready.\n",
"workflow overview release artifact validation action plan",
);
assertEqual(
releaseArtifactValidationActionPlanRenderState.statusLine,
"Ready: Artifact validation action plan complete",
"workflow overview release artifact validation action-plan render state",
);
assertEqual(
releaseArtifactValidationActionPlanDomReadinessReady.ready,
true,
"workflow overview release artifact validation action-plan DOM readiness",
);
assertEqual(
releaseArtifactValidationActionPlanRenderResult.rowIds.includes("next-command"),
true,
"workflow overview release artifact validation action-plan next-command row",
);
assertEqual(
releaseArtifactValidationActionPlanMountResult.ready,
true,
"workflow overview release artifact validation action-plan mount readiness",
);
assertEqual(
releaseArtifactValidationActionPlanStatus.textContent,
"Ready: Artifact validation action plan complete",
"workflow overview release artifact validation action-plan rendered status",
);
assertEqual(
releaseArtifactGateExecutionSummary.statusLine,
"Ready: All release gate evidence present",
"workflow overview release artifact gate execution summary",
);
assertEqual(
releaseArtifactGateExecutionSummaryRenderState.rows.length,
17,
"workflow overview release artifact gate execution summary render rows",
);
assertEqual(
releaseArtifactGateExecutionSummaryDomReadinessReady.ready,
true,
"workflow overview release artifact gate execution summary DOM readiness",
);
assertEqual(
releaseArtifactGateExecutionSummaryRenderResult.rowIds.includes("gate-ui-node-smokes"),
true,
"workflow overview release artifact gate execution summary SDK row",
);
assertEqual(
releaseArtifactGateExecutionSummaryMountResult.ready,
true,
"workflow overview release artifact gate execution summary mount readiness",
);
assertEqual(
releaseArtifactGateExecutionSummaryStatus.textContent,
"Ready: All release gate evidence present",
"workflow overview release artifact gate execution summary rendered status",
);
assertEqual(
releaseArtifactUrlGateExecutionSummary.statusLine,
"Ready: All release gate evidence present",
"workflow overview release artifact URL gate execution summary",
);
assertEqual(releaseArtifactUrlWorkflow.mountResult.ready, true, "workflow overview release artifact URL workflow mount");
assertEqual(releaseArtifactStatus.textContent, releaseArtifactRenderState.statusLine, "workflow overview release artifact rendered status");
assertEqual(
workflowDoc.querySelector("[data-handoff-workflow]").dataset.handoffScope,
"workflow",
"workflow overview DOM scope",
);
assertEqual(
workflowDoc.querySelector("[data-handoff-workflow-status]").textContent,
renderState.statusLine,
"workflow overview DOM status line",
);
assertEqual(
workflowDoc.querySelector('[data-handoff-workflow-value="blocking-reasons"]').textContent.includes("readonly-status"),
true,
"workflow overview DOM blocking reasons row",
);
assertEqual(
workflowDoc.querySelector('[data-handoff-workflow-value="api-surface-inventory"]').textContent,
"ready: 6 rows",
"workflow overview DOM API surface inventory row",
);
assertEqual(
workflowDoc.querySelector('[data-handoff-workflow-value="persistence-summary"]').textContent,
"waiting: 5 missing",
"workflow overview DOM persistence summary row",
);
document.getElementById("status").textContent = "browser_ini_workflow_overview_smoke=ok";
console.log("browser_ini_workflow_overview_smoke=ok");
} catch (error) {
document.getElementById("status").textContent = `browser_ini_workflow_overview_smoke=failed: ${error.message}`;
console.error(error);
throw error;
}
</script>
</body>
</html>

View File

@@ -0,0 +1,203 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>LinuxCNC OPFS Session Workflow Smoke</title>
</head>
<body>
<pre id="status">running</pre>
<script type="module">
import {
createLinuxCncIniSdk,
createLinuxCncInterpSdk,
createMachineSessionPersistenceSummary,
gcodeProgramPath,
loadGcodeProgram,
loadMachineSessionFromOpfs,
loadMachineSessionSnapshot,
loadMachineTextFiles,
machineFilePaths,
readMachineSessionReadiness,
saveGcodeProgram,
saveMachineSessionSnapshot,
saveMachineTextFiles,
} from "../../runtime/sdk/src/index.js";
const status = document.getElementById("status");
function assertEqual(actual, expected, label) {
if (actual !== expected) {
throw new Error(`${label}: expected ${expected}, got ${actual}`);
}
}
function assertIncludes(values, expected, label) {
if (!values.includes(expected)) {
throw new Error(`${label}: expected ${expected} in ${values.join(", ")}`);
}
}
try {
const machineId = "browser-opfs-session-gate";
const sessionId = "browser-opfs-session-gate-session";
const snapshotFilename = "browser-opfs-session-gate.json";
const gcodeFilename = "browser-opfs-session-gate.ngc";
const gcodeOpfsPath = gcodeProgramPath(gcodeFilename);
const paths = machineFilePaths(machineId);
const iniText = `[EMC]
MACHINE = browser-opfs-session-gate
[TRAJ]
LINEAR_UNITS = mm
COORDINATES = X Y Z
[KINS]
KINEMATICS = trivkins
JOINTS = 3
[RS274NGC]
PARAMETER_FILE = linuxcnc.var
[EMCIO]
TOOL_TABLE = tool.tbl
`;
const parameterText = [
"5161 0.0",
"5162 0.0",
"5220 1",
"5221 0.0",
"5399 0.0",
"",
].join("\n");
const toolTableText = "T1 P1 Z0.0 D0.125 ;browser opfs session gate\n";
const gcodeText = "G0 X0 Y0\nG1 X1 Y1 F20\nM2\n";
const initialReadiness = await readMachineSessionReadiness(machineId, {
gcodeFilename,
sessionId,
snapshotFilename,
});
assertEqual(initialReadiness.ready, false, "initial readiness");
assertIncludes(initialReadiness.missing, "ini", "initial missing INI");
assertIncludes(initialReadiness.missing, "parameters", "initial missing parameters");
assertIncludes(initialReadiness.missing, "toolTable", "initial missing tool table");
assertIncludes(initialReadiness.missing, "gcode", "initial missing G-code");
assertIncludes(initialReadiness.missing, "snapshot", "initial missing snapshot");
await saveMachineTextFiles(machineId, {
ini: iniText,
parameters: parameterText,
toolTable: toolTableText,
});
await saveGcodeProgram(gcodeFilename, gcodeText);
const snapshot = await saveMachineSessionSnapshot(sessionId, machineId, {
filename: snapshotFilename,
gcodeFilename,
createdAt: "2026-06-16T00:00:00.000Z",
metadata: {
workflow: "opfs-session-workflow-browser-gate",
defaultGcodeOpfsPath: gcodeOpfsPath,
snapshotLabel: `${sessionId}/${snapshotFilename}`,
},
});
const files = await loadMachineTextFiles(machineId);
assertEqual(files.ini, iniText, "loaded INI text");
assertEqual(files.parameters, parameterText, "loaded parameter text");
assertEqual(files.toolTable, toolTableText, "loaded tool table text");
assertEqual(await loadGcodeProgram(gcodeFilename), gcodeText, "loaded G-code text");
assertEqual(snapshot.payload.files.ini, paths.ini, "snapshot INI path");
assertEqual(snapshot.payload.files.parameters, paths.parameters, "snapshot parameter path");
assertEqual(snapshot.payload.files.toolTable, paths.toolTable, "snapshot tool table path");
assertEqual(snapshot.payload.files.gcode, gcodeOpfsPath, "snapshot G-code path");
assertEqual(
(await loadMachineSessionSnapshot(sessionId, machineId, { filename: snapshotFilename }))
.metadata.workflow,
"opfs-session-workflow-browser-gate",
"loaded snapshot workflow metadata",
);
const readyReadiness = await readMachineSessionReadiness(machineId, {
gcodeFilename,
sessionId,
snapshotFilename,
});
assertEqual(readyReadiness.ready, true, "ready readiness");
assertEqual(readyReadiness.phase, "ready", "ready phase");
assertEqual(readyReadiness.missing.length, 0, "ready missing count");
assertEqual(readyReadiness.checks.length, 4, "ready file check count");
assertEqual(readyReadiness.snapshotCheck.ok, true, "ready snapshot check");
const iniSdk = await createLinuxCncIniSdk();
const interpSdk = await createLinuxCncInterpSdk();
const loadedSession = await loadMachineSessionFromOpfs(interpSdk, machineId, {
iniSdk,
iniWasmPath: "/work/opfs-session-workflow.ini",
parameterWasmPath: "/work/opfs-session-workflow.var",
toolTableWasmPath: "/work/opfs-session-workflow.tbl",
});
assertEqual(loadedSession.machineId, machineId, "loaded session machine");
assertEqual(loadedSession.ini.opfsPath, paths.ini, "loaded session INI OPFS path");
assertEqual(loadedSession.ini.wasmPath, "/work/opfs-session-workflow.ini", "loaded session INI WASM path");
assertEqual(loadedSession.parameters.opfsPath, paths.parameters, "loaded session parameter OPFS path");
assertEqual(loadedSession.parameters.wasmPath, "/work/opfs-session-workflow.var", "loaded session parameter WASM path");
assertEqual(loadedSession.toolTable.opfsPath, paths.toolTable, "loaded session tool table OPFS path");
assertEqual(loadedSession.toolTable.wasmPath, "/work/opfs-session-workflow.tbl", "loaded session tool table WASM path");
const persistenceSummary = createMachineSessionPersistenceSummary({
state: {
badges: { opfs: "OPFS: ready" },
machineFiles: {
iniOpfsPath: paths.ini,
parameterOpfsPath: paths.parameters,
toolTableOpfsPath: paths.toolTable,
gcodeOpfsPath,
},
sessionSnapshot: {
snapshotLabel: `${sessionId}/${snapshotFilename}`,
iniOpfsPath: paths.ini,
parameterOpfsPath: paths.parameters,
toolTableOpfsPath: paths.toolTable,
gcodeOpfsPath,
workflow: "opfs-session-workflow-browser-gate",
},
sessionReadiness: readyReadiness,
sessionLoad: {
iniWasmPath: loadedSession.ini.wasmPath,
parameterWasmPath: loadedSession.parameters.wasmPath,
toolTableWasmPath: loadedSession.toolTable.wasmPath,
},
},
readonlyStatus: { ready: true },
handoffWorkflowSummary: { ready: true },
});
assertEqual(persistenceSummary.apiName, "machine-session-persistence-summary", "summary API");
assertEqual(persistenceSummary.ready, true, "summary ready");
assertEqual(persistenceSummary.counts.machineFiles, 4, "summary machine file count");
assertEqual(persistenceSummary.missing.length, 0, "summary missing count");
assertEqual(
persistenceSummary.rows.find(({ id }) => id === "session-readiness")?.value,
"ready",
"summary readiness row",
);
assertEqual(
persistenceSummary.rows.find(({ id }) => id === "session-load")?.value,
"loaded: /work/opfs-session-workflow.ini",
"summary load row",
);
assertEqual(
persistenceSummary.rows.find(({ id }) => id === "handoff-readiness")?.value,
"ready",
"summary handoff row",
);
status.textContent = "browser_opfs_session_workflow_smoke=ok";
console.log("browser_opfs_session_workflow_smoke=ok");
} catch (error) {
status.textContent = `browser_opfs_session_workflow_smoke=failed: ${error.message}`;
console.error(error);
throw error;
}
</script>
</body>
</html>

View File

@@ -0,0 +1,83 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>LinuxCNC Real Simulation Page Smoke</title>
</head>
<body>
<pre id="status">running</pre>
<script type="module">
const status = document.getElementById("status");
async function loadFrame(src) {
const frame = document.createElement("iframe");
frame.src = src;
frame.width = "1280";
frame.height = "800";
document.body.appendChild(frame);
await new Promise((resolve, reject) => {
frame.addEventListener("load", resolve, { once: true });
frame.addEventListener("error", reject, { once: true });
});
return frame;
}
async function waitForState(frame) {
for (let i = 0; i < 200; i += 1) {
const state = frame.contentWindow?.linuxCncRealSimulationState;
if (state?.summary?.ready) {
return state;
}
await new Promise((resolve) => setTimeout(resolve, 50));
}
throw new Error("real simulation page did not expose ready state");
}
try {
const frame = await loadFrame("../../runtime/ui/simulation/index.html");
const doc = frame.contentDocument;
const state = await waitForState(frame);
const polyline = doc.querySelector("[data-toolpath-polyline]");
const rows = [...doc.querySelectorAll("[data-motion-row]")];
const programRows = [...doc.querySelectorAll("[data-program-line]")];
const canonicalOutput = doc.querySelector("[data-canonical-output]")?.textContent ?? "";
if (doc.body.dataset.simulationReady !== "true") {
throw new Error("simulation body readiness flag not true");
}
if (state.apiName !== "real-browser-simulation-state") {
throw new Error("simulation state API name drift");
}
if (state.summary.motionEventCount < 5) {
throw new Error("simulation motion event count too low");
}
if (!canonicalOutput.includes("canon_event=STRAIGHT_FEED line=2 x=1 y=0 z=0")) {
throw new Error("simulation canonical output missing LinuxCNC feed event");
}
if (!polyline?.getAttribute("points")?.includes("1,0")) {
throw new Error("simulation toolpath polyline missing expected point");
}
if (rows.length !== state.motion.length) {
throw new Error("simulation motion table row count drift");
}
if (programRows.length < 6) {
throw new Error("simulation program rows missing");
}
if (doc.querySelector('[data-axis="x"]')?.textContent !== "0.000") {
throw new Error("simulation final X axis drift");
}
if (doc.querySelector('[data-axis="y"]')?.textContent !== "0.000") {
throw new Error("simulation final Y axis drift");
}
if (!doc.querySelector("[data-toolpath-svg]")?.getAttribute("viewBox")) {
throw new Error("simulation SVG viewBox missing");
}
status.textContent = "browser_real_simulation_page_smoke=ok";
} catch (error) {
status.textContent = `browser_real_simulation_page_smoke=fail ${error.stack || error.message}`;
throw error;
}
</script>
</body>
</html>

View File

@@ -0,0 +1,101 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>LinuxCNC Release Artifact URL Workflow Smoke</title>
</head>
<body>
<pre id="status">running</pre>
<script type="module">
function assertEqual(actual, expected, label) {
if (actual !== expected) {
throw new Error(`${label}: expected ${expected}, got ${actual}`);
}
}
async function loadWorkflowOverviewFrame() {
const frame = document.createElement("iframe");
frame.src = "../../runtime/ui/ini-panel/workflow-overview.html";
document.body.appendChild(frame);
await new Promise((resolve, reject) => {
frame.addEventListener("load", resolve, { once: true });
frame.addEventListener("error", reject, { once: true });
});
return frame.contentDocument;
}
try {
const workflowDoc = await loadWorkflowOverviewFrame();
const workflowApi = workflowDoc.defaultView.linuxCncIniPanelWorkflowOverviewApi;
if (
!workflowApi?.loadWorkflowOverviewReleaseReadinessArtifactUrl ||
!workflowApi?.getWorkflowOverviewReleaseReadinessArtifactUrlWorkflowSummaryViewModel
) {
throw new Error("workflow overview API did not expose release artifact URL workflow");
}
const missingUrl = await workflowApi.loadWorkflowOverviewReleaseReadinessArtifactUrl();
assertEqual(missingUrl.ready, false, "missing URL workflow readiness");
assertEqual(missingUrl.statusLine, "Blocked: artifact-url", "missing URL status");
const missingDom = await workflowApi.loadWorkflowOverviewReleaseReadinessArtifactUrl({
artifactUrl: "../../tests/fixtures/project-release-readiness-ready.json",
mount: true,
});
assertEqual(missingDom.ready, false, "missing DOM workflow readiness");
assertEqual(missingDom.validation.ready, true, "missing DOM validation readiness");
assertEqual(missingDom.missing.join(","), "mount,status,rows", "missing DOM reasons");
const releaseArtifactNode = workflowDoc.createElement("section");
releaseArtifactNode.dataset.workflowOverviewReleaseReadinessArtifact = "";
const releaseArtifactStatus = workflowDoc.createElement("p");
releaseArtifactStatus.dataset.workflowOverviewReleaseReadinessArtifactStatus = "";
const releaseArtifactRows = workflowDoc.createElement("dl");
releaseArtifactRows.dataset.workflowOverviewReleaseReadinessArtifactRows = "";
releaseArtifactNode.append(releaseArtifactStatus, releaseArtifactRows);
workflowDoc.body.append(releaseArtifactNode);
const readyWorkflow = await workflowApi.loadWorkflowOverviewReleaseReadinessArtifactUrl({
artifactUrl: "../../tests/fixtures/project-release-readiness-ready.json",
mount: true,
});
const readyWorkflowSummary = workflowApi
.getWorkflowOverviewReleaseReadinessArtifactUrlWorkflowSummaryViewModel(readyWorkflow);
assertEqual(
readyWorkflow.apiName,
"ini-panel-shell-workflow-overview-release-readiness-artifact-url-workflow",
"ready workflow API name",
);
assertEqual(readyWorkflow.ready, true, "ready workflow readiness");
assertEqual(readyWorkflow.httpStatus, 200, "ready workflow HTTP status");
assertEqual(readyWorkflow.validation.gateCount, 12, "ready workflow gate count");
assertEqual(
readyWorkflow.validationSummaryViewModel.statusLine,
"Ready: Artifact evidence complete",
"ready workflow validation summary",
);
assertEqual(
readyWorkflowSummary.statusLine,
"Ready: Artifact URL workflow complete",
"ready workflow summary view model",
);
assertEqual(readyWorkflow.renderState.statusLine, "Ready: No blocking reasons", "ready workflow status");
assertEqual(readyWorkflow.mountResult.ready, true, "ready workflow mount readiness");
assertEqual(readyWorkflow.mountResult.renderResult.rowCount, 6, "ready workflow mount rows");
assertEqual(
releaseArtifactStatus.textContent,
"Ready: No blocking reasons",
"ready workflow rendered status",
);
document.getElementById("status").textContent = "browser_release_artifact_url_workflow_smoke=ok";
console.log("browser_release_artifact_url_workflow_smoke=ok");
} catch (error) {
document.getElementById("status").textContent =
`browser_release_artifact_url_workflow_smoke=failed: ${error.message}`;
console.error(error);
throw error;
}
</script>
</body>
</html>

View File

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

View File

@@ -0,0 +1,81 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "$0")/../.." && pwd)"
CHROMIUM="${CHROMIUM:-$(command -v chromium || command -v chromium-browser || command -v google-chrome || command -v google-chrome-stable || true)}"
if [[ -z "$CHROMIUM" ]]; then
echo "missing Chromium-compatible browser; set CHROMIUM=/path/to/browser" >&2
exit 1
fi
if [[ "${SKIP_INI_BUILD:-0}" != "1" ]]; then
"$ROOT_DIR/tools/build_ini_panel.sh"
fi
if [[ "${SKIP_INTERP_BUILD:-0}" != "1" ]]; then
"$ROOT_DIR/tools/build_wasm_core.sh"
fi
TMP_DIR="$(mktemp -d)"
PORT_FILE="$TMP_DIR/port"
SERVER_LOG="$TMP_DIR/server.log"
CHROME_PROFILE="$TMP_DIR/chrome-profile"
mkdir -p "$CHROME_PROFILE"
cleanup() {
if [[ -n "${SERVER_PID:-}" ]]; then
kill "$SERVER_PID" 2>/dev/null || true
wait "$SERVER_PID" 2>/dev/null || true
fi
rm -rf "$TMP_DIR"
}
trap cleanup EXIT
python3 - <<'PY' "$ROOT_DIR" "$PORT_FILE" >"$SERVER_LOG" 2>&1 &
import functools
import http.server
import pathlib
import socketserver
import sys
root = pathlib.Path(sys.argv[1])
port_file = pathlib.Path(sys.argv[2])
handler = functools.partial(http.server.SimpleHTTPRequestHandler, directory=str(root))
with socketserver.TCPServer(("127.0.0.1", 0), handler) as httpd:
port_file.write_text(str(httpd.server_address[1]), encoding="ascii")
httpd.serve_forever()
PY
SERVER_PID=$!
for _ in $(seq 1 100); do
[[ -s "$PORT_FILE" ]] && break
sleep 0.05
done
if [[ ! -s "$PORT_FILE" ]]; then
echo "OPFS/session workflow browser smoke HTTP server did not start" >&2
cat "$SERVER_LOG" >&2 || true
exit 1
fi
PORT="$(cat "$PORT_FILE")"
URL="http://127.0.0.1:$PORT/tests/browser/opfs_session_workflow_smoke.html"
OUT="$TMP_DIR/chromium.out"
"$CHROMIUM" \
--headless=new \
--disable-gpu \
--no-sandbox \
--user-data-dir="$CHROME_PROFILE" \
--virtual-time-budget=10000 \
--dump-dom \
"$URL" >"$OUT" 2>&1
if ! grep -Fq "browser_opfs_session_workflow_smoke=ok" "$OUT"; then
echo "OPFS/session workflow browser smoke failed" >&2
sed -n '1,220p' "$OUT" >&2
exit 1
fi
echo "browser_opfs_session_workflow_smoke=ok"

View File

@@ -0,0 +1,78 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "$0")/../.." && pwd)"
CHROMIUM="${CHROMIUM:-$(command -v chromium || command -v chromium-browser || command -v google-chrome || command -v google-chrome-stable || true)}"
if [[ -z "$CHROMIUM" ]]; then
echo "missing Chromium-compatible browser; set CHROMIUM=/path/to/browser" >&2
exit 1
fi
if [[ "${SKIP_INTERP_BUILD:-0}" != "1" ]]; then
"$ROOT_DIR/tools/build_wasm_core.sh"
fi
TMP_DIR="$(mktemp -d)"
PORT_FILE="$TMP_DIR/port"
SERVER_LOG="$TMP_DIR/server.log"
CHROME_PROFILE="$TMP_DIR/chrome-profile"
mkdir -p "$CHROME_PROFILE"
cleanup() {
if [[ -n "${SERVER_PID:-}" ]]; then
kill "$SERVER_PID" 2>/dev/null || true
wait "$SERVER_PID" 2>/dev/null || true
fi
rm -rf "$TMP_DIR"
}
trap cleanup EXIT
python3 - <<'PY' "$ROOT_DIR" "$PORT_FILE" >"$SERVER_LOG" 2>&1 &
import functools
import http.server
import pathlib
import socketserver
import sys
root = pathlib.Path(sys.argv[1])
port_file = pathlib.Path(sys.argv[2])
handler = functools.partial(http.server.SimpleHTTPRequestHandler, directory=str(root))
with socketserver.TCPServer(("127.0.0.1", 0), handler) as httpd:
port_file.write_text(str(httpd.server_address[1]), encoding="ascii")
httpd.serve_forever()
PY
SERVER_PID=$!
for _ in $(seq 1 100); do
[[ -s "$PORT_FILE" ]] && break
sleep 0.05
done
if [[ ! -s "$PORT_FILE" ]]; then
echo "real simulation browser smoke HTTP server did not start" >&2
cat "$SERVER_LOG" >&2 || true
exit 1
fi
PORT="$(cat "$PORT_FILE")"
URL="http://127.0.0.1:$PORT/tests/browser/real_simulation_page_smoke.html"
OUT="$TMP_DIR/chromium-real-simulation.out"
"$CHROMIUM" \
--headless=new \
--disable-gpu \
--no-sandbox \
--user-data-dir="$CHROME_PROFILE" \
--virtual-time-budget=10000 \
--dump-dom \
"$URL" >"$OUT" 2>&1
if ! grep -Fq "browser_real_simulation_page_smoke=ok" "$OUT"; then
echo "browser real simulation page smoke failed" >&2
sed -n '1,260p' "$OUT" >&2
exit 1
fi
echo "browser_real_simulation_page_smoke=ok"

View File

@@ -0,0 +1,81 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "$0")/../.." && pwd)"
CHROMIUM="${CHROMIUM:-$(command -v chromium || command -v chromium-browser || command -v google-chrome || command -v google-chrome-stable || true)}"
if [[ -z "$CHROMIUM" ]]; then
echo "missing Chromium-compatible browser; set CHROMIUM=/path/to/browser" >&2
exit 1
fi
if [[ "${SKIP_INI_BUILD:-0}" != "1" ]]; then
"$ROOT_DIR/tools/build_ini_panel.sh"
fi
if [[ "${SKIP_INTERP_BUILD:-0}" != "1" ]]; then
"$ROOT_DIR/tools/build_wasm_core.sh"
fi
TMP_DIR="$(mktemp -d)"
PORT_FILE="$TMP_DIR/port"
SERVER_LOG="$TMP_DIR/server.log"
CHROME_PROFILE="$TMP_DIR/chrome-profile"
mkdir -p "$CHROME_PROFILE"
cleanup() {
if [[ -n "${SERVER_PID:-}" ]]; then
kill "$SERVER_PID" 2>/dev/null || true
wait "$SERVER_PID" 2>/dev/null || true
fi
rm -rf "$TMP_DIR"
}
trap cleanup EXIT
python3 - <<'PY' "$ROOT_DIR" "$PORT_FILE" >"$SERVER_LOG" 2>&1 &
import functools
import http.server
import pathlib
import socketserver
import sys
root = pathlib.Path(sys.argv[1])
port_file = pathlib.Path(sys.argv[2])
handler = functools.partial(http.server.SimpleHTTPRequestHandler, directory=str(root))
with socketserver.TCPServer(("127.0.0.1", 0), handler) as httpd:
port_file.write_text(str(httpd.server_address[1]), encoding="ascii")
httpd.serve_forever()
PY
SERVER_PID=$!
for _ in $(seq 1 100); do
[[ -s "$PORT_FILE" ]] && break
sleep 0.05
done
if [[ ! -s "$PORT_FILE" ]]; then
echo "release artifact URL workflow browser smoke HTTP server did not start" >&2
cat "$SERVER_LOG" >&2 || true
exit 1
fi
PORT="$(cat "$PORT_FILE")"
URL="http://127.0.0.1:$PORT/tests/browser/release_artifact_url_workflow_smoke.html"
OUT="$TMP_DIR/chromium.out"
"$CHROMIUM" \
--headless=new \
--disable-gpu \
--no-sandbox \
--user-data-dir="$CHROME_PROFILE" \
--virtual-time-budget=10000 \
--dump-dom \
"$URL" >"$OUT" 2>&1
if ! grep -Fq "browser_release_artifact_url_workflow_smoke=ok" "$OUT"; then
echo "release artifact URL workflow browser smoke failed" >&2
sed -n '1,220p' "$OUT" >&2
exit 1
fi
echo "browser_release_artifact_url_workflow_smoke=ok"

View File

@@ -0,0 +1,100 @@
import assert from "node:assert/strict";
import { existsSync, readFileSync } from "node:fs";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
const __dirname = dirname(fileURLToPath(import.meta.url));
const root = resolve(__dirname, "../../..");
function literalRegExp(text) {
return new RegExp(text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
}
function parseTsv(text) {
const [headerLine, ...lines] = text.trim().split(/\r?\n/);
const headers = headerLine.split("\t");
return lines.map((line) => {
const cells = line.split("\t");
return Object.fromEntries(headers.map((header, index) => [header, cells[index] ?? ""]));
});
}
const docText = readFileSync(resolve(root, "docs/host-runtime-boundary-handoff.md"), "utf8");
const fullProcessText = readFileSync(resolve(root, "docs/full-process-boundary-design.md"), "utf8");
const simHandoffText = readFileSync(resolve(root, "docs/sim-configs-coverage-handoff.md"), "utf8");
const inventoryTestText = readFileSync(
resolve(root, "tests/wasm/node/verify_sim_configs_inventory_wasm.mjs"),
"utf8",
);
const hostSmokeText = readFileSync(resolve(root, "tests/host/verify_host_smokes.sh"), "utf8");
const readmeText = readFileSync(resolve(root, "README.md"), "utf8");
const trackerText = readFileSync(resolve(root, "../PROJECT_COMPLETION_TRACKER.md"), "utf8");
for (const phrase of [
"Host/runtime boundary release handoff",
"runtime-boundary-host-readiness-rollup.tsv",
"runtime-boundary-promotion-blockers.tsv",
"runtime-boundary-opt-in-probe-dispatch-rollup.tsv",
"runtime-boundary-native-evidence-acceptance-gate.tsv",
"blocked-runtime-promotion-lock.tsv",
"host_readiness_status=host_blocked_for_all_opt_in_native_probes",
"missing_host_requirements=halcmd,halrun,linuxcnc,milltask",
"dispatch_status=dispatch_blocked_for_all_opt_in_native_probes",
"current_probe_status=skipped_missing_host_runtime",
"native_evidence_status=pending_until_native_pass",
"evidence_acceptance_allowed=0",
"ENABLE_MILLTURN_USER_M_RUNTIME_PROBE=1",
"ENABLE_TOOL_DB_RUNTIME_PROBE=1",
"ENABLE_PYTHON_REMAP_RUNTIME_PROBE=1",
"L4-USER-M-PROCESS",
"L4-TOOL-DB",
"L4-PYTHON-REMAP",
"host_runtime_boundary_docs_node_smoke=ok",
]) {
assert.match(docText, literalRegExp(phrase));
}
for (const phrase of [
"runtime-boundary-host-readiness-rollup.tsv",
"runtime-boundary-opt-in-probe-dispatch-rollup.tsv",
"runtime-boundary-native-evidence-acceptance-gate.tsv",
]) {
assert.match(fullProcessText, literalRegExp(phrase));
assert.match(inventoryTestText, literalRegExp(phrase));
assert.match(docText, literalRegExp(phrase));
}
assert.match(simHandoffText, /L4-PYTHON-REMAP/);
assert.match(hostSmokeText, /verify_host_runtime_boundary_docs\.sh/);
assert.match(readmeText, /docs\/host-runtime-boundary-handoff\.md/);
assert.match(trackerText, /Host\/runtime boundary/);
const hostRollupPath = resolve(
root,
"build/wasm/sim-configs-inventory/runtime-boundary-host-readiness-rollup.tsv",
);
if (existsSync(hostRollupPath)) {
const [row] = parseTsv(readFileSync(hostRollupPath, "utf8"));
assert.equal(row.host_readiness_status, "host_blocked_for_all_opt_in_native_probes");
assert.equal(row.family_count, "3");
assert.equal(row.host_ready_family_count, "0");
assert.equal(row.host_blocked_family_count, "3");
assert.equal(row.missing_host_requirements, "halcmd,halrun,linuxcnc,milltask");
assert.equal(row.execution_enabled, "0");
assert.equal(row.promotion_allowed, "0");
}
const dispatchRollupPath = resolve(
root,
"build/wasm/sim-configs-inventory/runtime-boundary-opt-in-probe-dispatch-rollup.tsv",
);
if (existsSync(dispatchRollupPath)) {
const [row] = parseTsv(readFileSync(dispatchRollupPath, "utf8"));
assert.equal(row.dispatch_status, "dispatch_blocked_for_all_opt_in_native_probes");
assert.equal(row.dispatch_allowed_count, "0");
assert.equal(row.dispatch_blocked_count, "3");
assert.equal(row.execution_enabled, "0");
assert.equal(row.promotion_allowed, "0");
}
console.log("host_runtime_boundary_docs_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/docs/node/verify_host_runtime_boundary_docs.mjs"

View File

@@ -0,0 +1,46 @@
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
const __dirname = dirname(fileURLToPath(import.meta.url));
const root = resolve(__dirname, "../../..");
const docText = readFileSync(resolve(root, "docs/opfs-session-persistence.md"), "utf8");
const readmeText = readFileSync(resolve(root, "README.md"), "utf8");
const sdkReadmeText = readFileSync(resolve(root, "runtime/sdk/README.md"), "utf8");
const hostSmokeText = readFileSync(resolve(root, "tests/host/verify_host_smokes.sh"), "utf8");
const browserSmokeText = readFileSync(resolve(root, "tests/browser/verify_ini_panel_browser.sh"), "utf8");
const trackerText = readFileSync(resolve(root, "../PROJECT_COMPLETION_TRACKER.md"), "utf8");
for (const phrase of [
"OPFS/session persistence and release gate",
"runtime/sdk/src/index.js",
"createMachineSessionSnapshotPayload",
"readMachineSessionReadiness",
"loadMachineSessionFromOpfs",
"restoreMachineParametersFromOpfs",
"loadMachineToolTableFromOpfs",
"browser_ini_opfs_smoke=ok",
"browser_opfs_session_workflow_smoke=ok",
"verify_opfs_session_workflow_browser.sh",
"sdk_surface_node_smoke=ok",
"host_wasm_opfs_browser_smokes=ok",
"verify_no_standalone_cnc_semantics.sh",
"L4-USER-M-PROCESS",
"L4-TOOL-DB",
"L4-PYTHON-REMAP",
]) {
assert.match(docText, new RegExp(phrase.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")));
}
assert.match(readmeText, /docs\/opfs-session-persistence\.md/);
assert.match(sdkReadmeText, /OPFS\/session persistence exports/);
assert.match(sdkReadmeText, /readMachineSessionReadiness/);
assert.match(hostSmokeText, /verify_sdk_surface\.sh/);
assert.match(hostSmokeText, /verify_file_service\.sh/);
assert.match(hostSmokeText, /verify_opfs_session_workflow_browser\.sh/);
assert.match(browserSmokeText, /browser_ini_opfs_smoke=ok/);
assert.match(trackerText, /OPFS\/session persistence/);
console.log("opfs_session_docs_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/docs/node/verify_opfs_session_docs.mjs"

View File

@@ -0,0 +1,262 @@
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
const __dirname = dirname(fileURLToPath(import.meta.url));
const root = resolve(__dirname, "../../..");
function literalRegExp(text) {
return new RegExp(text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
}
const handoffText = readFileSync(resolve(root, "docs/project-release-handoff.md"), "utf8");
const readmeText = readFileSync(resolve(root, "README.md"), "utf8");
const panelEntryText = readFileSync(resolve(root, "docs/panel-entry.md"), "utf8");
const sdkReadmeText = readFileSync(resolve(root, "runtime/sdk/README.md"), "utf8");
const realBrowserSimulationPriorityText = readFileSync(
resolve(root, "docs/real-browser-simulation-priority.md"),
"utf8",
);
const sourceReuseText = readFileSync(resolve(root, "docs/source-reuse-map.md"), "utf8");
const driftText = readFileSync(resolve(root, "docs/drift-report.md"), "utf8");
const hostSmokeText = readFileSync(resolve(root, "tests/host/verify_host_smokes.sh"), "utf8");
const projectReleaseGateText = readFileSync(resolve(root, "tests/host/verify_project_release_gate.sh"), "utf8");
const trackerText = readFileSync(resolve(root, "../PROJECT_COMPLETION_TRACKER.md"), "utf8");
for (const phrase of [
"Project release handoff",
"docs/panel-entry.md",
"runtime/sdk/README.md",
"docs/opfs-session-persistence.md",
"docs/real-browser-simulation-priority.md",
"docs/sim-configs-coverage-handoff.md",
"docs/host-runtime-boundary-handoff.md",
"docs/source-reuse-map.md",
"docs/drift-report.md",
"PROJECT_COMPLETION_TRACKER.md",
"runtime/sdk/src/index.js",
"real browser CNC simulation page",
"runtime/ui/simulation/index.html",
"verify_real_simulation_browser.sh",
"LinuxCNC-backed interpreter execution",
"toolpath/preview area",
"createProjectReleaseGateManifest()",
"createProjectReleaseGateResultMatrix()",
"createProjectReleaseGateActionPlan()",
"createProjectReleaseGateExecutionManifest()",
"createProjectReleaseGateExecutionSummaryViewModel()",
"createProjectReleaseReadinessReport()",
"createProjectReleaseReadinessSummaryViewModel()",
"parseProjectReleaseReadinessArtifactJson()",
"createProjectReleaseReadinessArtifactJsonWorkflow()",
"createProjectReleaseReadinessArtifactValidation()",
"createProjectReleaseReadinessArtifactValidationActionPlan()",
"createProjectReleaseReadinessArtifactValidationSummaryViewModel()",
"loadProjectReleaseReadinessArtifactUrlWorkflow()",
"createProjectReleaseReadinessArtifactUrlWorkflowActionPlan()",
"createProjectReleaseReadinessArtifactUrlWorkflowSummaryViewModel()",
"createProjectBatchAcceptanceCapabilityMatrix()",
"createProjectBatchAcceptanceWorkflow()",
"createProjectBatchAcceptanceChecklist()",
"createProjectBatchAcceptanceReport()",
"createProjectBatchAcceptanceReportValidation()",
"createProjectBatchAcceptanceReportValidationSummaryViewModel()",
"createProjectBatchAcceptanceReportValidationActionPlan()",
"createProjectBatchAcceptanceReportJsonWorkflow()",
"loadProjectBatchAcceptanceReportUrlWorkflow()",
"createProjectBatchAcceptanceReportUrlWorkflowSummaryViewModel()",
"createProjectBatchAcceptanceReportUrlWorkflowActionPlan()",
"write_project_batch_acceptance_artifact.mjs",
"verify_project_batch_acceptance_artifact.sh",
"embedded gate manifest, execution manifest, result matrix, and",
"validateWorkflowOverviewReleaseReadinessArtifactJson()",
"runWorkflowOverviewReleaseReadinessArtifactJsonWorkflow()",
"getWorkflowOverviewReleaseReadinessArtifactJsonWorkflowSummaryViewModel()",
"getWorkflowOverviewReleaseReadinessArtifactJsonWorkflowActionPlan()",
"getWorkflowOverviewReleaseReadinessArtifactJsonWorkflowDomContract()",
"getWorkflowOverviewReleaseReadinessArtifactJsonWorkflowDomReadiness()",
"renderWorkflowOverviewReleaseReadinessArtifactJsonWorkflowState()",
"mountWorkflowOverviewReleaseReadinessArtifactJsonWorkflowState()",
"loadWorkflowOverviewReleaseReadinessArtifactUrl()",
"getWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryViewModel()",
"getWorkflowOverviewReleaseReadinessArtifactUrlGateExecutionSummaryViewModel()",
"getWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryRenderState()",
"getWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryDomContract()",
"getWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryDomReadiness()",
"renderWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryState()",
"mountWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryState()",
"getWorkflowOverviewReleaseReadinessArtifactUrlWorkflowActionPlan()",
"getWorkflowOverviewReleaseReadinessArtifactUrlWorkflowRenderState()",
"getWorkflowOverviewReleaseReadinessArtifactUrlWorkflowDomContract()",
"getWorkflowOverviewReleaseReadinessArtifactUrlWorkflowDomReadiness()",
"renderWorkflowOverviewReleaseReadinessArtifactUrlWorkflowState()",
"mountWorkflowOverviewReleaseReadinessArtifactUrlWorkflowState()",
"getWorkflowOverviewReleaseReadinessArtifactValidationActionPlan()",
"getWorkflowOverviewReleaseReadinessArtifactValidationActionPlanRenderState()",
"getWorkflowOverviewReleaseReadinessArtifactValidationActionPlanDomContract()",
"getWorkflowOverviewReleaseReadinessArtifactValidationActionPlanDomReadiness()",
"renderWorkflowOverviewReleaseReadinessArtifactValidationActionPlanState()",
"mountWorkflowOverviewReleaseReadinessArtifactValidationActionPlanState()",
"getWorkflowOverviewReleaseReadinessArtifactUrlWorkflowSummaryViewModel()",
"render-state",
"DOM readiness",
"createIniPanelShellApiSurfaceInventory()",
"createIniPanelShellSessionReadinessWorkflowReport()",
"workflow overview embedding mount DOM",
"verify_no_standalone_cnc_semantics.sh",
"verify_host_smokes.sh",
"verify_project_release_gate.sh",
"build/project-release-readiness.json",
"verify_project_release_readiness_artifact.sh",
"verify_project_release_artifact_url_workflow.sh",
"verify_project_batch_acceptance_workflow.sh",
"verify_real_browser_simulation_priority_docs.sh",
"verify_opfs_session_workflow_browser.sh",
"host_wasm_opfs_browser_smokes=ok",
"project_release_readiness_artifact_node_smoke=ok",
"project_release_artifact_url_workflow_node_smoke=ok",
"project_batch_acceptance_workflow_node_smoke=ok",
"project_batch_acceptance_artifact_node_smoke=ok",
"real_browser_simulation_priority_docs_node_smoke=ok",
"project_release_gate=ok",
"sim_configs_wasm_node_inventory_unexpected_fail=0",
"browser_ini_shell_integration_workflow_smoke=ok",
"browser_opfs_session_workflow_smoke=ok",
"browser_release_artifact_url_workflow_smoke=ok",
"browser_real_simulation_page_smoke=ok",
"project_release_handoff_docs_node_smoke=ok",
"L4-USER-M-PROCESS",
"L4-TOOL-DB",
"L4-PYTHON-REMAP",
]) {
assert.match(handoffText, literalRegExp(phrase));
}
for (const phrase of [
"docs/project-release-handoff.md",
"docs/real-browser-simulation-priority.md",
"project_release_handoff_docs_node_smoke=ok",
"real_browser_simulation_priority_docs_node_smoke=ok",
]) {
assert.match(readmeText, literalRegExp(phrase));
assert.match(hostSmokeText, literalRegExp("verify_project_release_handoff_docs.sh"));
}
for (const phrase of [
"git diff --check",
"verify_vendor_sync.sh",
"verify_no_standalone_cnc_semantics.sh",
"verify_interp_wasm.sh",
"verify_sim_configs_inventory_wasm.sh",
"verify_ini_panel_browser.sh",
"verify_opfs_session_workflow_browser.sh",
"verify_release_artifact_url_workflow_browser.sh",
"verify_project_batch_acceptance_workflow.sh",
"verify_real_browser_simulation_priority_docs.sh",
"verify_ui_node_smokes.sh",
"verify_host_smokes.sh",
"write_project_release_readiness_artifact.mjs",
"verify_project_release_readiness_artifact.sh",
"verify_project_release_artifact_url_workflow.sh",
"verify_project_batch_acceptance_workflow.sh",
"verify_real_browser_simulation_priority_docs.sh",
"project_release_gate=ok",
]) {
assert.match(projectReleaseGateText, literalRegExp(phrase));
}
for (const phrase of [
"docs/project-release-handoff.md",
"runtime/sdk/src/index.js",
"workflow overview embedding",
]) {
assert.match(panelEntryText, literalRegExp(phrase));
}
for (const phrase of [
"OPFS/session persistence exports",
"Project release readiness export",
"createProjectReleaseGateManifest()",
"createProjectReleaseGateResultMatrix()",
"createProjectReleaseGateActionPlan()",
"createProjectReleaseGateExecutionSummaryViewModel()",
"createProjectReleaseReadinessReport()",
"createProjectReleaseReadinessSummaryViewModel()",
"parseProjectReleaseReadinessArtifactJson()",
"createProjectReleaseReadinessArtifactJsonWorkflow()",
"createProjectReleaseReadinessArtifactValidation()",
"createProjectReleaseReadinessArtifactValidationActionPlan()",
"createProjectReleaseReadinessArtifactValidationSummaryViewModel()",
"gateManifestReady",
"gateExecutionManifestReady",
"gateExecutionSummaryReady",
"gateResultMatrixReady",
"gateActionPlanReady",
"loadIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrl()",
"loadProjectReleaseReadinessArtifactUrlWorkflow()",
"createProjectReleaseReadinessArtifactUrlWorkflowActionPlan()",
"createProjectReleaseReadinessArtifactUrlWorkflowSummaryViewModel()",
"createProjectBatchAcceptanceCapabilityMatrix()",
"createProjectBatchAcceptanceWorkflow()",
"createProjectBatchAcceptanceChecklist()",
"createProjectBatchAcceptanceReport()",
"createProjectBatchAcceptanceReportValidation()",
"createProjectBatchAcceptanceReportValidationSummaryViewModel()",
"createProjectBatchAcceptanceReportValidationActionPlan()",
"createProjectBatchAcceptanceReportJsonWorkflow()",
"loadProjectBatchAcceptanceReportUrlWorkflow()",
"createProjectBatchAcceptanceReportUrlWorkflowSummaryViewModel()",
"createProjectBatchAcceptanceReportUrlWorkflowActionPlan()",
"createProjectBatchAcceptanceSummaryViewModel()",
"createProjectBatchAcceptanceActionPlan()",
"createIniPanelShellWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryViewModel()",
"createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlGateExecutionSummaryViewModel()",
"createIniPanelShellWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryRenderState()",
"createIniPanelShellWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryDomContract()",
"createIniPanelShellWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryDomReadiness()",
"renderIniPanelShellWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryState()",
"mountIniPanelShellWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryState()",
"createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowActionPlan()",
"createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowRenderState()",
"createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowDomContract()",
"createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowDomReadiness()",
"renderIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowState()",
"mountIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowState()",
"createIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationActionPlan()",
"createIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationActionPlanRenderState()",
"createIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationActionPlanDomContract()",
"createIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationActionPlanDomReadiness()",
"renderIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationActionPlanState()",
"mountIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationActionPlanState()",
"runIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflow()",
"createIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflowSummaryViewModel()",
"createIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflowActionPlan()",
"createIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflowDomContract()",
"createIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflowDomReadiness()",
"renderIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflowState()",
"mountIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflowState()",
"verify_project_release_artifact_url_workflow.sh",
"verify_project_batch_acceptance_workflow.sh",
"verify_project_batch_acceptance_artifact.sh",
"project_release_artifact_url_workflow_node_smoke=ok",
"project_batch_acceptance_workflow_node_smoke=ok",
"project_batch_acceptance_artifact_node_smoke=ok",
"build/project-release-readiness.json",
"INI panel shell handoff exports",
"createIniPanelShellApiSurfaceInventory()",
"createIniPanelShellSessionReadinessWorkflowReport()",
]) {
assert.match(sdkReadmeText, literalRegExp(phrase));
}
assert.match(sourceReuseText, literalRegExp("docs/project-release-handoff.md"));
assert.match(driftText, literalRegExp("docs/project-release-handoff.md"));
assert.match(realBrowserSimulationPriorityText, literalRegExp("first priority is to deliver a real browser CNC simulation page"));
assert.match(realBrowserSimulationPriorityText, literalRegExp("must not implement CNC semantics in JavaScript"));
assert.match(trackerText, /Documentation\/release gate/);
assert.match(trackerText, /Real browser simulation priority/);
assert.match(trackerText, /Project release gate wrapper/);
assert.match(trackerText, /Final Acceptance Checklist/);
console.log("project_release_handoff_docs_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/docs/node/verify_project_release_handoff_docs.mjs"

View File

@@ -0,0 +1,53 @@
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
const __dirname = dirname(fileURLToPath(import.meta.url));
const root = resolve(__dirname, "../../..");
function literalRegExp(text) {
return new RegExp(text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
}
const priorityText = readFileSync(resolve(root, "docs/real-browser-simulation-priority.md"), "utf8");
const handoffText = readFileSync(resolve(root, "docs/project-release-handoff.md"), "utf8");
const readmeText = readFileSync(resolve(root, "README.md"), "utf8");
const trackerText = readFileSync(resolve(root, "../PROJECT_COMPLETION_TRACKER.md"), "utf8");
const hostSmokeText = readFileSync(resolve(root, "tests/host/verify_host_smokes.sh"), "utf8");
const releaseGateText = readFileSync(resolve(root, "tests/host/verify_project_release_gate.sh"), "utf8");
const simulationHtmlText = readFileSync(resolve(root, "runtime/ui/simulation/index.html"), "utf8");
const simulationSmokeText = readFileSync(resolve(root, "tests/browser/verify_real_simulation_browser.sh"), "utf8");
for (const phrase of [
"Real Browser Simulation Priority",
"first priority is to deliver a real browser CNC simulation page",
"machine/session loader",
"G-code program view",
"LinuxCNC-backed interpreter execution",
"toolpath/preview area",
"browser smoke coverage",
"must not implement CNC semantics in JavaScript",
"vendored LinuxCNC source",
"wasm-port/tests/docs/node/verify_real_browser_simulation_priority_docs.sh",
"runtime/ui/simulation/index.html",
"wasm-port/tests/browser/verify_real_simulation_browser.sh",
]) {
assert.match(priorityText, literalRegExp(phrase));
}
for (const text of [handoffText, readmeText, trackerText]) {
assert.match(text, literalRegExp("docs/real-browser-simulation-priority.md"));
assert.match(text, /real browser CNC simulation page|真实 UI\/browser 数控仿真页面/);
assert.match(text, /LinuxCNC semantic boundary|LinuxCNC 语义边界|LinuxCNC-backed/);
}
assert.match(hostSmokeText, /verify_real_browser_simulation_priority_docs\.sh/);
assert.match(hostSmokeText, /verify_real_simulation_browser\.sh/);
assert.match(releaseGateText, /verify_real_simulation_browser\.sh/);
assert.match(simulationHtmlText, /LinuxCNC Browser Simulation/);
assert.match(simulationHtmlText, /data-toolpath-svg/);
assert.match(simulationHtmlText, /data-program-lines/);
assert.match(simulationSmokeText, /browser_real_simulation_page_smoke=ok/);
console.log("real_browser_simulation_priority_docs_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/docs/node/verify_real_browser_simulation_priority_docs.mjs"

View File

@@ -0,0 +1,89 @@
import assert from "node:assert/strict";
import { existsSync, readFileSync } from "node:fs";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
const __dirname = dirname(fileURLToPath(import.meta.url));
const root = resolve(__dirname, "../../..");
function literalRegExp(text) {
return new RegExp(text.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
}
function parseTsv(text) {
const [headerLine, ...lines] = text.trim().split(/\r?\n/);
const headers = headerLine.split("\t");
return lines.map((line) => {
const cells = line.split("\t");
return Object.fromEntries(headers.map((header, index) => [header, cells[index] ?? ""]));
});
}
const docText = readFileSync(resolve(root, "docs/sim-configs-coverage-handoff.md"), "utf8");
const matrixText = readFileSync(resolve(root, "docs/sim-configs-coverage-matrix.md"), "utf8");
const completionPlanText = readFileSync(resolve(root, "docs/sim-configs-completion-plan.md"), "utf8");
const inventoryTestText = readFileSync(
resolve(root, "tests/wasm/node/verify_sim_configs_inventory_wasm.mjs"),
"utf8",
);
const hostSmokeText = readFileSync(resolve(root, "tests/host/verify_host_smokes.sh"), "utf8");
const trackerText = readFileSync(resolve(root, "../PROJECT_COMPLETION_TRACKER.md"), "utf8");
for (const phrase of [
"Sim-config coverage release handoff",
"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",
"sim_configs_wasm_node_inventory_skip_ASSET_ONLY=65",
"sim_configs_wasm_node_inventory_skip_L4_PYTHON_REMAP=53",
"sim_configs_wasm_node_inventory_skip_L4_TOOL_DB=1",
"sim_configs_wasm_node_inventory_skip_L4_USER_M_PROCESS=1",
"sim_configs_wasm_node_inventory_skip_NON_MAIN_CLASS=10",
"sim_configs_wasm_node_inventory_skip_UPSTREAM_DEMO=1",
"build/wasm/sim-configs-inventory/skip-summary.tsv",
"build/wasm/sim-configs-inventory/blocked-dependency-summary.tsv",
"verify_no_standalone_cnc_semantics.sh",
"verify_sim_configs_coverage_docs.sh",
"L4-PYTHON-REMAP",
"L4-TOOL-DB",
"L4-USER-M-PROCESS",
]) {
assert.match(docText, literalRegExp(phrase));
}
for (const reason of [
"ASSET-ONLY",
"L4-PYTHON-REMAP",
"L4-TOOL-DB",
"L4-USER-M-PROCESS",
"NON_MAIN_CLASS",
"UPSTREAM-DEMO",
]) {
assert.match(matrixText, literalRegExp(reason));
assert.match(completionPlanText, literalRegExp(reason));
assert.match(docText, literalRegExp(reason));
}
assert.match(inventoryTestText, /sim_configs_wasm_node_inventory_skip_/);
assert.match(hostSmokeText, /verify_sim_configs_inventory_wasm\.sh/);
assert.match(hostSmokeText, /verify_sim_configs_coverage_docs\.sh/);
assert.match(trackerText, /Sim config coverage|sim-config coverage/);
const skipSummaryPath = resolve(root, "build/wasm/sim-configs-inventory/skip-summary.tsv");
if (existsSync(skipSummaryPath)) {
const rows = parseTsv(readFileSync(skipSummaryPath, "utf8"));
assert.deepEqual(
Object.fromEntries(rows.map((row) => [row.skip_or_block_reason, Number(row.count)])),
{
"ASSET-ONLY": 65,
"L4-PYTHON-REMAP": 53,
"L4-TOOL-DB": 1,
"L4-USER-M-PROCESS": 1,
NON_MAIN_CLASS: 10,
"UPSTREAM-DEMO": 1,
},
);
}
console.log("sim_configs_coverage_docs_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/docs/node/verify_sim_configs_coverage_docs.mjs"

View File

@@ -0,0 +1,873 @@
{
"apiName": "project-release-readiness-report",
"reportVersion": 1,
"phase": "ready",
"ready": true,
"missing": [],
"releaseGate": {
"command": "wasm-port/tests/host/verify_project_release_gate.sh",
"passed": true,
"expectedOutput": "project_release_gate=ok"
},
"simConfigInventory": {
"executed": 28,
"passed": 28,
"skipped": 131,
"unexpectedFail": 0
},
"blockedRuntimeFamilies": [
"L4-USER-M-PROCESS",
"L4-TOOL-DB",
"L4-PYTHON-REMAP"
],
"promotedRuntimeFamilies": [],
"promotedBlockedFamilies": [],
"gateManifest": {
"apiName": "project-release-gate-manifest",
"manifestVersion": 1,
"gateCount": 12,
"gateIds": [
"diff-check",
"vendor-sync",
"standalone-cnc-semantics",
"interp-wasm",
"sim-config-inventory",
"ini-panel-browser",
"opfs-session-browser",
"release-artifact-url-browser",
"project-batch-acceptance",
"ui-node-smokes",
"host-smokes",
"project-release-gate"
],
"gates": [
{
"id": "diff-check",
"label": "Git diff check",
"command": "git diff --check",
"expectedOutput": null
},
{
"id": "vendor-sync",
"label": "Vendor sync guard",
"command": "wasm-port/tools/verify_vendor_sync.sh",
"expectedOutput": "vendor sync up to date"
},
{
"id": "standalone-cnc-semantics",
"label": "Standalone CNC semantics guard",
"command": "wasm-port/tools/verify_no_standalone_cnc_semantics.sh",
"expectedOutput": "standalone CNC semantics guard complete"
},
{
"id": "interp-wasm",
"label": "Interpreter WASM Node smoke",
"command": "SKIP_INTERP_BUILD=1 wasm-port/tests/wasm/node/verify_interp_wasm.sh",
"expectedOutput": "interp_wasm_node_smoke=ok"
},
{
"id": "sim-config-inventory",
"label": "Sim config inventory WASM Node smoke",
"command": "SKIP_INTERP_BUILD=1 wasm-port/tests/wasm/node/verify_sim_configs_inventory_wasm.sh",
"expectedOutput": "sim_configs_wasm_node_inventory_unexpected_fail=0"
},
{
"id": "ini-panel-browser",
"label": "INI panel browser smoke",
"command": "SKIP_INI_BUILD=1 SKIP_INTERP_BUILD=1 wasm-port/tests/browser/verify_ini_panel_browser.sh",
"expectedOutput": "browser_ini_shell_integration_workflow_smoke=ok"
},
{
"id": "opfs-session-browser",
"label": "OPFS session browser workflow smoke",
"command": "SKIP_INI_BUILD=1 SKIP_INTERP_BUILD=1 wasm-port/tests/browser/verify_opfs_session_workflow_browser.sh",
"expectedOutput": "browser_opfs_session_workflow_smoke=ok"
},
{
"id": "release-artifact-url-browser",
"label": "Release artifact URL browser workflow smoke",
"command": "SKIP_INI_BUILD=1 SKIP_INTERP_BUILD=1 wasm-port/tests/browser/verify_release_artifact_url_workflow_browser.sh",
"expectedOutput": "browser_release_artifact_url_workflow_smoke=ok"
},
{
"id": "project-batch-acceptance",
"label": "Project batch acceptance workflow smoke",
"command": "wasm-port/tests/sdk/node/verify_project_batch_acceptance_workflow.sh",
"expectedOutput": "project_batch_acceptance_workflow_node_smoke=ok"
},
{
"id": "ui-node-smokes",
"label": "UI Node smokes",
"command": "wasm-port/tests/ui/node/verify_ui_node_smokes.sh",
"expectedOutput": "ui_node_smokes=ok"
},
{
"id": "host-smokes",
"label": "Host aggregate smoke",
"command": "wasm-port/tests/host/verify_host_smokes.sh",
"expectedOutput": "host_wasm_opfs_browser_smokes=ok"
},
{
"id": "project-release-gate",
"label": "Project release gate",
"command": "wasm-port/tests/host/verify_project_release_gate.sh",
"expectedOutput": "project_release_gate=ok"
}
]
},
"gateExecutionManifest": {
"apiName": "project-release-gate-execution-manifest",
"manifestVersion": 1,
"phase": "ready",
"ready": true,
"gateCount": 12,
"passedCount": 12,
"unknownCount": 0,
"missingGateIds": [],
"manifest": {
"apiName": "project-release-gate-manifest",
"manifestVersion": 1,
"gateCount": 12,
"gateIds": [
"diff-check",
"vendor-sync",
"standalone-cnc-semantics",
"interp-wasm",
"sim-config-inventory",
"ini-panel-browser",
"opfs-session-browser",
"release-artifact-url-browser",
"project-batch-acceptance",
"ui-node-smokes",
"host-smokes",
"project-release-gate"
],
"gates": [
{
"id": "diff-check",
"label": "Git diff check",
"command": "git diff --check",
"expectedOutput": null
},
{
"id": "vendor-sync",
"label": "Vendor sync guard",
"command": "wasm-port/tools/verify_vendor_sync.sh",
"expectedOutput": "vendor sync up to date"
},
{
"id": "standalone-cnc-semantics",
"label": "Standalone CNC semantics guard",
"command": "wasm-port/tools/verify_no_standalone_cnc_semantics.sh",
"expectedOutput": "standalone CNC semantics guard complete"
},
{
"id": "interp-wasm",
"label": "Interpreter WASM Node smoke",
"command": "SKIP_INTERP_BUILD=1 wasm-port/tests/wasm/node/verify_interp_wasm.sh",
"expectedOutput": "interp_wasm_node_smoke=ok"
},
{
"id": "sim-config-inventory",
"label": "Sim config inventory WASM Node smoke",
"command": "SKIP_INTERP_BUILD=1 wasm-port/tests/wasm/node/verify_sim_configs_inventory_wasm.sh",
"expectedOutput": "sim_configs_wasm_node_inventory_unexpected_fail=0"
},
{
"id": "ini-panel-browser",
"label": "INI panel browser smoke",
"command": "SKIP_INI_BUILD=1 SKIP_INTERP_BUILD=1 wasm-port/tests/browser/verify_ini_panel_browser.sh",
"expectedOutput": "browser_ini_shell_integration_workflow_smoke=ok"
},
{
"id": "opfs-session-browser",
"label": "OPFS session browser workflow smoke",
"command": "SKIP_INI_BUILD=1 SKIP_INTERP_BUILD=1 wasm-port/tests/browser/verify_opfs_session_workflow_browser.sh",
"expectedOutput": "browser_opfs_session_workflow_smoke=ok"
},
{
"id": "release-artifact-url-browser",
"label": "Release artifact URL browser workflow smoke",
"command": "SKIP_INI_BUILD=1 SKIP_INTERP_BUILD=1 wasm-port/tests/browser/verify_release_artifact_url_workflow_browser.sh",
"expectedOutput": "browser_release_artifact_url_workflow_smoke=ok"
},
{
"id": "project-batch-acceptance",
"label": "Project batch acceptance workflow smoke",
"command": "wasm-port/tests/sdk/node/verify_project_batch_acceptance_workflow.sh",
"expectedOutput": "project_batch_acceptance_workflow_node_smoke=ok"
},
{
"id": "ui-node-smokes",
"label": "UI Node smokes",
"command": "wasm-port/tests/ui/node/verify_ui_node_smokes.sh",
"expectedOutput": "ui_node_smokes=ok"
},
{
"id": "host-smokes",
"label": "Host aggregate smoke",
"command": "wasm-port/tests/host/verify_host_smokes.sh",
"expectedOutput": "host_wasm_opfs_browser_smokes=ok"
},
{
"id": "project-release-gate",
"label": "Project release gate",
"command": "wasm-port/tests/host/verify_project_release_gate.sh",
"expectedOutput": "project_release_gate=ok"
}
]
},
"rows": [
{
"id": "diff-check",
"label": "Git diff check",
"command": "git diff --check",
"expectedOutput": null,
"observedOutput": null,
"observedOutputCount": 11,
"status": "passed",
"passed": true,
"evidence": "explicit-result"
},
{
"id": "vendor-sync",
"label": "Vendor sync guard",
"command": "wasm-port/tools/verify_vendor_sync.sh",
"expectedOutput": "vendor sync up to date",
"observedOutput": "vendor sync up to date",
"observedOutputCount": 11,
"status": "passed",
"passed": true,
"evidence": "expected-output"
},
{
"id": "standalone-cnc-semantics",
"label": "Standalone CNC semantics guard",
"command": "wasm-port/tools/verify_no_standalone_cnc_semantics.sh",
"expectedOutput": "standalone CNC semantics guard complete",
"observedOutput": "standalone CNC semantics guard complete",
"observedOutputCount": 11,
"status": "passed",
"passed": true,
"evidence": "expected-output"
},
{
"id": "interp-wasm",
"label": "Interpreter WASM Node smoke",
"command": "SKIP_INTERP_BUILD=1 wasm-port/tests/wasm/node/verify_interp_wasm.sh",
"expectedOutput": "interp_wasm_node_smoke=ok",
"observedOutput": "interp_wasm_node_smoke=ok",
"observedOutputCount": 11,
"status": "passed",
"passed": true,
"evidence": "expected-output"
},
{
"id": "sim-config-inventory",
"label": "Sim config inventory WASM Node smoke",
"command": "SKIP_INTERP_BUILD=1 wasm-port/tests/wasm/node/verify_sim_configs_inventory_wasm.sh",
"expectedOutput": "sim_configs_wasm_node_inventory_unexpected_fail=0",
"observedOutput": "sim_configs_wasm_node_inventory_unexpected_fail=0",
"observedOutputCount": 11,
"status": "passed",
"passed": true,
"evidence": "expected-output"
},
{
"id": "ini-panel-browser",
"label": "INI panel browser smoke",
"command": "SKIP_INI_BUILD=1 SKIP_INTERP_BUILD=1 wasm-port/tests/browser/verify_ini_panel_browser.sh",
"expectedOutput": "browser_ini_shell_integration_workflow_smoke=ok",
"observedOutput": "browser_ini_shell_integration_workflow_smoke=ok",
"observedOutputCount": 11,
"status": "passed",
"passed": true,
"evidence": "expected-output"
},
{
"id": "opfs-session-browser",
"label": "OPFS session browser workflow smoke",
"command": "SKIP_INI_BUILD=1 SKIP_INTERP_BUILD=1 wasm-port/tests/browser/verify_opfs_session_workflow_browser.sh",
"expectedOutput": "browser_opfs_session_workflow_smoke=ok",
"observedOutput": "browser_opfs_session_workflow_smoke=ok",
"observedOutputCount": 11,
"status": "passed",
"passed": true,
"evidence": "expected-output"
},
{
"id": "release-artifact-url-browser",
"label": "Release artifact URL browser workflow smoke",
"command": "SKIP_INI_BUILD=1 SKIP_INTERP_BUILD=1 wasm-port/tests/browser/verify_release_artifact_url_workflow_browser.sh",
"expectedOutput": "browser_release_artifact_url_workflow_smoke=ok",
"observedOutput": "browser_release_artifact_url_workflow_smoke=ok",
"observedOutputCount": 11,
"status": "passed",
"passed": true,
"evidence": "expected-output"
},
{
"id": "project-batch-acceptance",
"label": "Project batch acceptance workflow smoke",
"command": "wasm-port/tests/sdk/node/verify_project_batch_acceptance_workflow.sh",
"expectedOutput": "project_batch_acceptance_workflow_node_smoke=ok",
"observedOutput": "project_batch_acceptance_workflow_node_smoke=ok",
"observedOutputCount": 11,
"status": "passed",
"passed": true,
"evidence": "expected-output"
},
{
"id": "ui-node-smokes",
"label": "UI Node smokes",
"command": "wasm-port/tests/ui/node/verify_ui_node_smokes.sh",
"expectedOutput": "ui_node_smokes=ok",
"observedOutput": "ui_node_smokes=ok",
"observedOutputCount": 11,
"status": "passed",
"passed": true,
"evidence": "expected-output"
},
{
"id": "host-smokes",
"label": "Host aggregate smoke",
"command": "wasm-port/tests/host/verify_host_smokes.sh",
"expectedOutput": "host_wasm_opfs_browser_smokes=ok",
"observedOutput": "host_wasm_opfs_browser_smokes=ok",
"observedOutputCount": 11,
"status": "passed",
"passed": true,
"evidence": "expected-output"
},
{
"id": "project-release-gate",
"label": "Project release gate",
"command": "wasm-port/tests/host/verify_project_release_gate.sh",
"expectedOutput": "project_release_gate=ok",
"observedOutput": "project_release_gate=ok",
"observedOutputCount": 11,
"status": "passed",
"passed": true,
"evidence": "expected-output"
}
]
},
"gateExecutionSummaryViewModel": {
"apiName": "project-release-gate-execution-summary-view-model",
"viewModelVersion": 1,
"phase": "ready",
"ready": true,
"title": "Project release gate execution",
"statusText": "Ready",
"detailText": "All release gate evidence present",
"statusLine": "Ready: All release gate evidence present",
"gateCount": 12,
"passedCount": 12,
"unknownCount": 0,
"missingGateIds": [],
"nextGateId": null,
"evidenceCounts": {
"explicit-result": 1,
"expected-output": 11
},
"rows": [
{
"id": "gate-results",
"label": "Release gate evidence",
"value": "12/12 passed"
},
{
"id": "unknown-gates",
"label": "Unknown gates",
"value": "0"
},
{
"id": "next-gate",
"label": "Next gate",
"value": "none"
},
{
"id": "expected-output-evidence",
"label": "Expected-output evidence",
"value": "11"
},
{
"id": "explicit-result-evidence",
"label": "Explicit-result evidence",
"value": "1"
},
{
"id": "missing",
"label": "Missing gate evidence",
"value": "none"
}
],
"gateRows": [
{
"id": "diff-check",
"label": "Git diff check",
"command": "git diff --check",
"expectedOutput": null,
"observedOutput": null,
"evidence": "explicit-result",
"status": "passed",
"passed": true
},
{
"id": "vendor-sync",
"label": "Vendor sync guard",
"command": "wasm-port/tools/verify_vendor_sync.sh",
"expectedOutput": "vendor sync up to date",
"observedOutput": "vendor sync up to date",
"evidence": "expected-output",
"status": "passed",
"passed": true
},
{
"id": "standalone-cnc-semantics",
"label": "Standalone CNC semantics guard",
"command": "wasm-port/tools/verify_no_standalone_cnc_semantics.sh",
"expectedOutput": "standalone CNC semantics guard complete",
"observedOutput": "standalone CNC semantics guard complete",
"evidence": "expected-output",
"status": "passed",
"passed": true
},
{
"id": "interp-wasm",
"label": "Interpreter WASM Node smoke",
"command": "SKIP_INTERP_BUILD=1 wasm-port/tests/wasm/node/verify_interp_wasm.sh",
"expectedOutput": "interp_wasm_node_smoke=ok",
"observedOutput": "interp_wasm_node_smoke=ok",
"evidence": "expected-output",
"status": "passed",
"passed": true
},
{
"id": "sim-config-inventory",
"label": "Sim config inventory WASM Node smoke",
"command": "SKIP_INTERP_BUILD=1 wasm-port/tests/wasm/node/verify_sim_configs_inventory_wasm.sh",
"expectedOutput": "sim_configs_wasm_node_inventory_unexpected_fail=0",
"observedOutput": "sim_configs_wasm_node_inventory_unexpected_fail=0",
"evidence": "expected-output",
"status": "passed",
"passed": true
},
{
"id": "ini-panel-browser",
"label": "INI panel browser smoke",
"command": "SKIP_INI_BUILD=1 SKIP_INTERP_BUILD=1 wasm-port/tests/browser/verify_ini_panel_browser.sh",
"expectedOutput": "browser_ini_shell_integration_workflow_smoke=ok",
"observedOutput": "browser_ini_shell_integration_workflow_smoke=ok",
"evidence": "expected-output",
"status": "passed",
"passed": true
},
{
"id": "opfs-session-browser",
"label": "OPFS session browser workflow smoke",
"command": "SKIP_INI_BUILD=1 SKIP_INTERP_BUILD=1 wasm-port/tests/browser/verify_opfs_session_workflow_browser.sh",
"expectedOutput": "browser_opfs_session_workflow_smoke=ok",
"observedOutput": "browser_opfs_session_workflow_smoke=ok",
"evidence": "expected-output",
"status": "passed",
"passed": true
},
{
"id": "release-artifact-url-browser",
"label": "Release artifact URL browser workflow smoke",
"command": "SKIP_INI_BUILD=1 SKIP_INTERP_BUILD=1 wasm-port/tests/browser/verify_release_artifact_url_workflow_browser.sh",
"expectedOutput": "browser_release_artifact_url_workflow_smoke=ok",
"observedOutput": "browser_release_artifact_url_workflow_smoke=ok",
"evidence": "expected-output",
"status": "passed",
"passed": true
},
{
"id": "project-batch-acceptance",
"label": "Project batch acceptance workflow smoke",
"command": "wasm-port/tests/sdk/node/verify_project_batch_acceptance_workflow.sh",
"expectedOutput": "project_batch_acceptance_workflow_node_smoke=ok",
"observedOutput": "project_batch_acceptance_workflow_node_smoke=ok",
"evidence": "expected-output",
"status": "passed",
"passed": true
},
{
"id": "ui-node-smokes",
"label": "UI Node smokes",
"command": "wasm-port/tests/ui/node/verify_ui_node_smokes.sh",
"expectedOutput": "ui_node_smokes=ok",
"observedOutput": "ui_node_smokes=ok",
"evidence": "expected-output",
"status": "passed",
"passed": true
},
{
"id": "host-smokes",
"label": "Host aggregate smoke",
"command": "wasm-port/tests/host/verify_host_smokes.sh",
"expectedOutput": "host_wasm_opfs_browser_smokes=ok",
"observedOutput": "host_wasm_opfs_browser_smokes=ok",
"evidence": "expected-output",
"status": "passed",
"passed": true
},
{
"id": "project-release-gate",
"label": "Project release gate",
"command": "wasm-port/tests/host/verify_project_release_gate.sh",
"expectedOutput": "project_release_gate=ok",
"observedOutput": "project_release_gate=ok",
"evidence": "expected-output",
"status": "passed",
"passed": true
}
]
},
"gateResultMatrix": {
"apiName": "project-release-gate-result-matrix",
"matrixVersion": 1,
"phase": "ready",
"ready": true,
"gateCount": 12,
"passedCount": 12,
"unknownCount": 0,
"missingGateIds": [],
"manifest": {
"apiName": "project-release-gate-manifest",
"manifestVersion": 1,
"gateCount": 12,
"gateIds": [
"diff-check",
"vendor-sync",
"standalone-cnc-semantics",
"interp-wasm",
"sim-config-inventory",
"ini-panel-browser",
"opfs-session-browser",
"release-artifact-url-browser",
"project-batch-acceptance",
"ui-node-smokes",
"host-smokes",
"project-release-gate"
],
"gates": [
{
"id": "diff-check",
"label": "Git diff check",
"command": "git diff --check",
"expectedOutput": null
},
{
"id": "vendor-sync",
"label": "Vendor sync guard",
"command": "wasm-port/tools/verify_vendor_sync.sh",
"expectedOutput": "vendor sync up to date"
},
{
"id": "standalone-cnc-semantics",
"label": "Standalone CNC semantics guard",
"command": "wasm-port/tools/verify_no_standalone_cnc_semantics.sh",
"expectedOutput": "standalone CNC semantics guard complete"
},
{
"id": "interp-wasm",
"label": "Interpreter WASM Node smoke",
"command": "SKIP_INTERP_BUILD=1 wasm-port/tests/wasm/node/verify_interp_wasm.sh",
"expectedOutput": "interp_wasm_node_smoke=ok"
},
{
"id": "sim-config-inventory",
"label": "Sim config inventory WASM Node smoke",
"command": "SKIP_INTERP_BUILD=1 wasm-port/tests/wasm/node/verify_sim_configs_inventory_wasm.sh",
"expectedOutput": "sim_configs_wasm_node_inventory_unexpected_fail=0"
},
{
"id": "ini-panel-browser",
"label": "INI panel browser smoke",
"command": "SKIP_INI_BUILD=1 SKIP_INTERP_BUILD=1 wasm-port/tests/browser/verify_ini_panel_browser.sh",
"expectedOutput": "browser_ini_shell_integration_workflow_smoke=ok"
},
{
"id": "opfs-session-browser",
"label": "OPFS session browser workflow smoke",
"command": "SKIP_INI_BUILD=1 SKIP_INTERP_BUILD=1 wasm-port/tests/browser/verify_opfs_session_workflow_browser.sh",
"expectedOutput": "browser_opfs_session_workflow_smoke=ok"
},
{
"id": "release-artifact-url-browser",
"label": "Release artifact URL browser workflow smoke",
"command": "SKIP_INI_BUILD=1 SKIP_INTERP_BUILD=1 wasm-port/tests/browser/verify_release_artifact_url_workflow_browser.sh",
"expectedOutput": "browser_release_artifact_url_workflow_smoke=ok"
},
{
"id": "project-batch-acceptance",
"label": "Project batch acceptance workflow smoke",
"command": "wasm-port/tests/sdk/node/verify_project_batch_acceptance_workflow.sh",
"expectedOutput": "project_batch_acceptance_workflow_node_smoke=ok"
},
{
"id": "ui-node-smokes",
"label": "UI Node smokes",
"command": "wasm-port/tests/ui/node/verify_ui_node_smokes.sh",
"expectedOutput": "ui_node_smokes=ok"
},
{
"id": "host-smokes",
"label": "Host aggregate smoke",
"command": "wasm-port/tests/host/verify_host_smokes.sh",
"expectedOutput": "host_wasm_opfs_browser_smokes=ok"
},
{
"id": "project-release-gate",
"label": "Project release gate",
"command": "wasm-port/tests/host/verify_project_release_gate.sh",
"expectedOutput": "project_release_gate=ok"
}
]
},
"rows": [
{
"id": "diff-check",
"label": "Git diff check",
"command": "git diff --check",
"expectedOutput": null,
"status": "passed",
"passed": true
},
{
"id": "vendor-sync",
"label": "Vendor sync guard",
"command": "wasm-port/tools/verify_vendor_sync.sh",
"expectedOutput": "vendor sync up to date",
"status": "passed",
"passed": true
},
{
"id": "standalone-cnc-semantics",
"label": "Standalone CNC semantics guard",
"command": "wasm-port/tools/verify_no_standalone_cnc_semantics.sh",
"expectedOutput": "standalone CNC semantics guard complete",
"status": "passed",
"passed": true
},
{
"id": "interp-wasm",
"label": "Interpreter WASM Node smoke",
"command": "SKIP_INTERP_BUILD=1 wasm-port/tests/wasm/node/verify_interp_wasm.sh",
"expectedOutput": "interp_wasm_node_smoke=ok",
"status": "passed",
"passed": true
},
{
"id": "sim-config-inventory",
"label": "Sim config inventory WASM Node smoke",
"command": "SKIP_INTERP_BUILD=1 wasm-port/tests/wasm/node/verify_sim_configs_inventory_wasm.sh",
"expectedOutput": "sim_configs_wasm_node_inventory_unexpected_fail=0",
"status": "passed",
"passed": true
},
{
"id": "ini-panel-browser",
"label": "INI panel browser smoke",
"command": "SKIP_INI_BUILD=1 SKIP_INTERP_BUILD=1 wasm-port/tests/browser/verify_ini_panel_browser.sh",
"expectedOutput": "browser_ini_shell_integration_workflow_smoke=ok",
"status": "passed",
"passed": true
},
{
"id": "opfs-session-browser",
"label": "OPFS session browser workflow smoke",
"command": "SKIP_INI_BUILD=1 SKIP_INTERP_BUILD=1 wasm-port/tests/browser/verify_opfs_session_workflow_browser.sh",
"expectedOutput": "browser_opfs_session_workflow_smoke=ok",
"status": "passed",
"passed": true
},
{
"id": "release-artifact-url-browser",
"label": "Release artifact URL browser workflow smoke",
"command": "SKIP_INI_BUILD=1 SKIP_INTERP_BUILD=1 wasm-port/tests/browser/verify_release_artifact_url_workflow_browser.sh",
"expectedOutput": "browser_release_artifact_url_workflow_smoke=ok",
"status": "passed",
"passed": true
},
{
"id": "project-batch-acceptance",
"label": "Project batch acceptance workflow smoke",
"command": "wasm-port/tests/sdk/node/verify_project_batch_acceptance_workflow.sh",
"expectedOutput": "project_batch_acceptance_workflow_node_smoke=ok",
"status": "passed",
"passed": true
},
{
"id": "ui-node-smokes",
"label": "UI Node smokes",
"command": "wasm-port/tests/ui/node/verify_ui_node_smokes.sh",
"expectedOutput": "ui_node_smokes=ok",
"status": "passed",
"passed": true
},
{
"id": "host-smokes",
"label": "Host aggregate smoke",
"command": "wasm-port/tests/host/verify_host_smokes.sh",
"expectedOutput": "host_wasm_opfs_browser_smokes=ok",
"status": "passed",
"passed": true
},
{
"id": "project-release-gate",
"label": "Project release gate",
"command": "wasm-port/tests/host/verify_project_release_gate.sh",
"expectedOutput": "project_release_gate=ok",
"status": "passed",
"passed": true
}
]
},
"gateActionPlan": {
"apiName": "project-release-gate-action-plan",
"planVersion": 1,
"phase": "ready",
"ready": true,
"gateCount": 12,
"completedCount": 12,
"pendingCount": 0,
"completedGateIds": [
"diff-check",
"vendor-sync",
"standalone-cnc-semantics",
"interp-wasm",
"sim-config-inventory",
"ini-panel-browser",
"opfs-session-browser",
"release-artifact-url-browser",
"project-batch-acceptance",
"ui-node-smokes",
"host-smokes",
"project-release-gate"
],
"pendingGateIds": [],
"nextGateId": null,
"nextCommand": null,
"commands": [],
"shellScript": "# Project release gates are already marked passed.\n",
"rows": []
},
"gates": [
{
"id": "diff-check",
"label": "Git diff check",
"command": "git diff --check",
"expectedOutput": null,
"status": "passed",
"passed": true
},
{
"id": "vendor-sync",
"label": "Vendor sync guard",
"command": "wasm-port/tools/verify_vendor_sync.sh",
"expectedOutput": "vendor sync up to date",
"status": "passed",
"passed": true
},
{
"id": "standalone-cnc-semantics",
"label": "Standalone CNC semantics guard",
"command": "wasm-port/tools/verify_no_standalone_cnc_semantics.sh",
"expectedOutput": "standalone CNC semantics guard complete",
"status": "passed",
"passed": true
},
{
"id": "interp-wasm",
"label": "Interpreter WASM Node smoke",
"command": "SKIP_INTERP_BUILD=1 wasm-port/tests/wasm/node/verify_interp_wasm.sh",
"expectedOutput": "interp_wasm_node_smoke=ok",
"status": "passed",
"passed": true
},
{
"id": "sim-config-inventory",
"label": "Sim config inventory WASM Node smoke",
"command": "SKIP_INTERP_BUILD=1 wasm-port/tests/wasm/node/verify_sim_configs_inventory_wasm.sh",
"expectedOutput": "sim_configs_wasm_node_inventory_unexpected_fail=0",
"status": "passed",
"passed": true
},
{
"id": "ini-panel-browser",
"label": "INI panel browser smoke",
"command": "SKIP_INI_BUILD=1 SKIP_INTERP_BUILD=1 wasm-port/tests/browser/verify_ini_panel_browser.sh",
"expectedOutput": "browser_ini_shell_integration_workflow_smoke=ok",
"status": "passed",
"passed": true
},
{
"id": "opfs-session-browser",
"label": "OPFS session browser workflow smoke",
"command": "SKIP_INI_BUILD=1 SKIP_INTERP_BUILD=1 wasm-port/tests/browser/verify_opfs_session_workflow_browser.sh",
"expectedOutput": "browser_opfs_session_workflow_smoke=ok",
"status": "passed",
"passed": true
},
{
"id": "release-artifact-url-browser",
"label": "Release artifact URL browser workflow smoke",
"command": "SKIP_INI_BUILD=1 SKIP_INTERP_BUILD=1 wasm-port/tests/browser/verify_release_artifact_url_workflow_browser.sh",
"expectedOutput": "browser_release_artifact_url_workflow_smoke=ok",
"status": "passed",
"passed": true
},
{
"id": "project-batch-acceptance",
"label": "Project batch acceptance workflow smoke",
"command": "wasm-port/tests/sdk/node/verify_project_batch_acceptance_workflow.sh",
"expectedOutput": "project_batch_acceptance_workflow_node_smoke=ok",
"status": "passed",
"passed": true
},
{
"id": "ui-node-smokes",
"label": "UI Node smokes",
"command": "wasm-port/tests/ui/node/verify_ui_node_smokes.sh",
"expectedOutput": "ui_node_smokes=ok",
"status": "passed",
"passed": true
},
{
"id": "host-smokes",
"label": "Host aggregate smoke",
"command": "wasm-port/tests/host/verify_host_smokes.sh",
"expectedOutput": "host_wasm_opfs_browser_smokes=ok",
"status": "passed",
"passed": true
},
{
"id": "project-release-gate",
"label": "Project release gate",
"command": "wasm-port/tests/host/verify_project_release_gate.sh",
"expectedOutput": "project_release_gate=ok",
"status": "passed",
"passed": true
}
],
"rows": [
{
"id": "project-release-gate",
"label": "Project release gate",
"value": "passed"
},
{
"id": "sim-config-inventory",
"label": "Sim config inventory",
"value": "unexpected_fail=0"
},
{
"id": "blocked-runtime-families",
"label": "Blocked runtime families",
"value": "L4-USER-M-PROCESS, L4-TOOL-DB, L4-PYTHON-REMAP"
}
]
}

View File

@@ -13,8 +13,16 @@ SKIP_INTERP_BUILD=1 "$ROOT_DIR/tests/wasm/node/verify_sim_configs_inventory_wasm
SKIP_INTERP_BUILD=1 "$ROOT_DIR/tests/wasm/node/verify_nc_files_wasm.sh"
SKIP_TP_BUILD=1 "$ROOT_DIR/tests/wasm/node/verify_tp_wasm.sh"
"$ROOT_DIR/tests/opfs/node/verify_file_service.sh"
"$ROOT_DIR/tests/sdk/node/verify_sdk_surface.sh"
"$ROOT_DIR/tests/docs/node/verify_opfs_session_docs.sh"
"$ROOT_DIR/tests/docs/node/verify_sim_configs_coverage_docs.sh"
"$ROOT_DIR/tests/docs/node/verify_host_runtime_boundary_docs.sh"
"$ROOT_DIR/tests/docs/node/verify_project_release_handoff_docs.sh"
"$ROOT_DIR/tests/docs/node/verify_real_browser_simulation_priority_docs.sh"
"$ROOT_DIR/tests/ui/node/verify_ui_node_smokes.sh"
SKIP_INI_BUILD=1 SKIP_INTERP_BUILD=1 "$ROOT_DIR/tests/browser/verify_ini_panel_browser.sh"
SKIP_INTERP_BUILD=1 "$ROOT_DIR/tests/browser/verify_real_simulation_browser.sh"
SKIP_INI_BUILD=1 SKIP_INTERP_BUILD=1 "$ROOT_DIR/tests/browser/verify_opfs_session_workflow_browser.sh"
SKIP_INI_BUILD=1 SKIP_INTERP_BUILD=1 "$ROOT_DIR/tests/browser/verify_interp_browser.sh"
echo "host_wasm_opfs_browser_smokes=ok"

View File

@@ -0,0 +1,27 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "$0")/../.." && pwd)"
REPO_ROOT="$(cd "$ROOT_DIR/.." && pwd)"
cd "$REPO_ROOT"
git diff --check
"$ROOT_DIR/tools/verify_vendor_sync.sh"
"$ROOT_DIR/tools/verify_no_standalone_cnc_semantics.sh"
SKIP_INTERP_BUILD=1 "$ROOT_DIR/tests/wasm/node/verify_interp_wasm.sh"
SKIP_INTERP_BUILD=1 "$ROOT_DIR/tests/wasm/node/verify_sim_configs_inventory_wasm.sh"
SKIP_INI_BUILD=1 SKIP_INTERP_BUILD=1 "$ROOT_DIR/tests/browser/verify_ini_panel_browser.sh"
SKIP_INTERP_BUILD=1 "$ROOT_DIR/tests/browser/verify_real_simulation_browser.sh"
SKIP_INI_BUILD=1 SKIP_INTERP_BUILD=1 "$ROOT_DIR/tests/browser/verify_opfs_session_workflow_browser.sh"
SKIP_INI_BUILD=1 SKIP_INTERP_BUILD=1 "$ROOT_DIR/tests/browser/verify_release_artifact_url_workflow_browser.sh"
"$ROOT_DIR/tests/sdk/node/verify_project_batch_acceptance_workflow.sh"
"$ROOT_DIR/tests/ui/node/verify_ui_node_smokes.sh"
"$ROOT_DIR/tests/sdk/node/verify_project_release_artifact_url_workflow.sh"
"$ROOT_DIR/tests/docs/node/verify_real_browser_simulation_priority_docs.sh"
"$ROOT_DIR/tests/host/verify_host_smokes.sh"
node "$ROOT_DIR/tests/host/write_project_release_readiness_artifact.mjs"
"$ROOT_DIR/tests/host/verify_project_release_readiness_artifact.sh"
echo "project_release_gate=ok"

View File

@@ -0,0 +1,54 @@
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import {
createProjectReleaseReadinessArtifactValidation,
parseProjectReleaseReadinessArtifactJson,
} from "../../runtime/sdk/src/index.js";
const __dirname = dirname(fileURLToPath(import.meta.url));
const root = resolve(__dirname, "../..");
const artifactPath = process.argv[2] ?? resolve(root, "build/project-release-readiness.json");
const artifact = parseProjectReleaseReadinessArtifactJson(readFileSync(artifactPath, "utf8"));
const validation = createProjectReleaseReadinessArtifactValidation(artifact);
assert.equal(validation.apiName, "project-release-readiness-artifact-validation");
assert.equal(validation.ready, true);
assert.equal(validation.phase, "ready");
assert.deepEqual(validation.missing, []);
assert.equal(validation.artifactApiName, "project-release-readiness-report");
assert.equal(validation.artifactVersion, 1);
assert.equal(validation.gateCount, 12);
assert.equal(validation.expectedGateCount, 12);
assert.equal(validation.gateManifestReady, true);
assert.equal(validation.gateExecutionManifestReady, true);
assert.equal(validation.gateExecutionSummaryReady, true);
assert.equal(validation.gateResultMatrixReady, true);
assert.equal(validation.gateActionPlanReady, true);
assert.deepEqual(validation.expectedGateIds, [
"diff-check",
"vendor-sync",
"standalone-cnc-semantics",
"interp-wasm",
"sim-config-inventory",
"ini-panel-browser",
"opfs-session-browser",
"release-artifact-url-browser",
"project-batch-acceptance",
"ui-node-smokes",
"host-smokes",
"project-release-gate",
]);
assert.equal(artifact.gateManifest.apiName, "project-release-gate-manifest");
assert.equal(artifact.gateExecutionManifest.apiName, "project-release-gate-execution-manifest");
assert.equal(artifact.gateExecutionManifest.passedCount, 12);
assert.equal(artifact.gateExecutionSummaryViewModel.apiName, "project-release-gate-execution-summary-view-model");
assert.equal(artifact.gateExecutionSummaryViewModel.rows[0].value, "12/12 passed");
assert.equal(artifact.gateResultMatrix.apiName, "project-release-gate-result-matrix");
assert.equal(artifact.gateActionPlan.apiName, "project-release-gate-action-plan");
assert.equal(artifact.gateActionPlan.pendingCount, 0);
assert.equal(artifact.gateActionPlan.nextCommand, null);
console.log("project_release_readiness_artifact_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/host/verify_project_release_readiness_artifact.mjs" "$@"

View File

@@ -0,0 +1,47 @@
import { mkdirSync, renameSync, writeFileSync } from "node:fs";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { createProjectReleaseReadinessReport } from "../../runtime/sdk/src/index.js";
const __dirname = dirname(fileURLToPath(import.meta.url));
const root = resolve(__dirname, "../..");
const outputPath = process.argv[2] ?? resolve(root, "build/project-release-readiness.json");
const passedGateResults = {
"diff-check": true,
"vendor-sync": true,
"standalone-cnc-semantics": true,
"interp-wasm": true,
"sim-config-inventory": true,
"ini-panel-browser": true,
"opfs-session-browser": true,
"release-artifact-url-browser": true,
"project-batch-acceptance": true,
"ui-node-smokes": true,
"host-smokes": true,
"project-release-gate": true,
};
const report = createProjectReleaseReadinessReport({
gateResults: passedGateResults,
observedOutputs: [
"vendor sync up to date",
"standalone CNC semantics guard complete",
"interp_wasm_node_smoke=ok",
"sim_configs_wasm_node_inventory_unexpected_fail=0",
"browser_ini_shell_integration_workflow_smoke=ok",
"browser_opfs_session_workflow_smoke=ok",
"browser_release_artifact_url_workflow_smoke=ok",
"project_batch_acceptance_workflow_node_smoke=ok",
"ui_node_smokes=ok",
"host_wasm_opfs_browser_smokes=ok",
"project_release_gate=ok",
],
});
mkdirSync(dirname(outputPath), { recursive: true });
writeFileSync(`${outputPath}.tmp`, `${JSON.stringify(report, null, 2)}\n`);
renameSync(`${outputPath}.tmp`, outputPath);
console.log(`project_release_readiness_artifact=${outputPath}`);

View File

@@ -43,6 +43,9 @@ import {
import {
loadMachineSessionFromOpfs,
} from "../../../runtime/opfs/linuxcnc-machine-session-bridge.js";
import {
readMachineSessionReadiness,
} from "../../../runtime/opfs/machine-session-readiness.js";
class MockFileHandle {
constructor(name) {
@@ -420,6 +423,54 @@ assertGcodeProgramStoragePath(
);
assert.equal(await loadGcodeProgram("custom-fixture.ngc", { storage }), "G1 X1 F10\nM2\n");
const readyMachineSession = await readMachineSessionReadiness("xyzab-tdr", {
storage,
sessionId: "machine-session-1",
snapshotFilename: "machine-session-snapshot.json",
gcodeFilename: "fixture.ngc",
});
assert.equal(readyMachineSession.ready, true);
assert.equal(readyMachineSession.phase, "ready");
assert.deepEqual(readyMachineSession.missing, []);
assert.deepEqual(readyMachineSession.errors, []);
assert.deepEqual(
readyMachineSession.checks.map((check) => [check.name, check.ok, check.path]),
[
["ini", true, "linuxcnc/machines/xyzab-tdr/machine.ini"],
["parameters", true, "linuxcnc/machines/xyzab-tdr/linuxcnc.var"],
["toolTable", true, "linuxcnc/machines/xyzab-tdr/tool.tbl"],
["gcode", true, "linuxcnc/gcode/fixture.ngc"],
],
);
assert.deepEqual(readyMachineSession.snapshotCheck, {
name: "snapshot",
path: "linuxcnc/sessions/machine-session-1/machine-session-snapshot.json",
ok: true,
error: null,
});
assert.equal(readyMachineSession.snapshot.sessionId, "machine-session-1");
const missingMachineSession = await readMachineSessionReadiness("missing-machine", {
storage,
sessionId: "missing-session",
gcodeRequired: true,
gcodeFilename: "missing.ngc",
});
assert.equal(missingMachineSession.ready, false);
assert.equal(missingMachineSession.phase, "blocked");
assert.deepEqual(missingMachineSession.missing, [
"ini",
"parameters",
"toolTable",
"gcode",
"snapshot",
]);
assert.deepEqual(
missingMachineSession.errors.map((error) => error.name),
["ini", "parameters", "toolTable", "gcode", "snapshot"],
);
assert.match(missingMachineSession.errors[0].error, /missing directory|missing file/);
const bridgeFiles = new Map();
const bridgeInterp = {
writeTextFile(path, text) {
@@ -897,6 +948,13 @@ await assert.rejects(
() => loadGcodeProgram("../escape.ngc", { storage }),
/Invalid G-code filename/,
);
await assert.rejects(
() => readMachineSessionReadiness("xyzab-tdr", {
storage,
gcodeOpfsPath: "../escape.ngc",
}),
/Invalid OPFS path/,
);
await assert.rejects(
() => saveSessionSnapshot("session-1", {}, {
storage,

View File

@@ -0,0 +1,51 @@
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import {
createProjectBatchAcceptanceReportJsonWorkflow,
createProjectBatchAcceptanceReportValidationActionPlan,
createProjectBatchAcceptanceReportValidationSummaryViewModel,
createProjectBatchAcceptanceReportValidation,
} from "../../../runtime/sdk/src/index.js";
const __dirname = dirname(fileURLToPath(import.meta.url));
const root = resolve(__dirname, "../../..");
const artifactPath = process.argv[2] ?? resolve(root, "build/project-batch-acceptance.json");
const artifactJson = readFileSync(artifactPath, "utf8");
const jsonWorkflow = createProjectBatchAcceptanceReportJsonWorkflow({ reportJson: artifactJson });
const validation = createProjectBatchAcceptanceReportValidation(jsonWorkflow.report);
const validationSummary = createProjectBatchAcceptanceReportValidationSummaryViewModel(validation);
const validationActionPlan = createProjectBatchAcceptanceReportValidationActionPlan(validation);
assert.equal(jsonWorkflow.apiName, "project-batch-acceptance-report-json-workflow");
assert.equal(jsonWorkflow.ready, true);
assert.equal(jsonWorkflow.phase, "ready");
assert.equal(jsonWorkflow.parsed, true);
assert.equal(jsonWorkflow.parseError, null);
assert.deepEqual(jsonWorkflow.missing, []);
assert.equal(validation.apiName, "project-batch-acceptance-report-validation");
assert.equal(validation.ready, true);
assert.equal(validation.phase, "ready");
assert.deepEqual(validation.missing, []);
assert.equal(validation.batchId, "project-batch-acceptance-artifact");
assert.equal(jsonWorkflow.report.apiName, "project-batch-acceptance-report");
assert.equal(jsonWorkflow.report.reportVersion, 1);
assert.equal(jsonWorkflow.report.ready, true);
assert.equal(jsonWorkflow.report.capabilityMatrix.ready, true);
assert.deepEqual(jsonWorkflow.report.capabilityMatrix.capabilityTypes, ["workflow", "gate"]);
assert.equal(jsonWorkflow.report.workflow.ready, true);
assert.equal(jsonWorkflow.report.summaryViewModel.statusLine, "Ready: Batch acceptance evidence complete");
assert.equal(jsonWorkflow.report.actionPlan.nextCommand, null);
assert.equal(jsonWorkflow.report.checklist.passedCount, 3);
assert.equal(jsonWorkflow.report.rows.find(({ id }) => id === "checklist")?.value, "3/3 passed");
assert.equal(validationSummary.apiName, "project-batch-acceptance-report-validation-summary-view-model");
assert.equal(validationSummary.statusLine, "Ready: Batch acceptance report evidence complete");
assert.equal(validationSummary.rows.find(({ id }) => id === "validation")?.value, "ready");
assert.equal(validationActionPlan.apiName, "project-batch-acceptance-report-validation-action-plan");
assert.equal(validationActionPlan.ready, true);
assert.equal(validationActionPlan.nextCommand, null);
assert.equal(validationActionPlan.shellScript, "# Project batch acceptance report validation is already ready.\n");
console.log("project_batch_acceptance_artifact_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/sdk/node/verify_project_batch_acceptance_artifact.mjs" "$@"

View File

@@ -0,0 +1,507 @@
import assert from "node:assert/strict";
import {
createProjectBatchAcceptanceActionPlan,
createProjectBatchAcceptanceCapabilityMatrix,
createProjectBatchAcceptanceChecklist,
createProjectBatchAcceptanceReport,
createProjectBatchAcceptanceReportJsonWorkflow,
createProjectBatchAcceptanceReportUrlWorkflowActionPlan,
createProjectBatchAcceptanceReportUrlWorkflowSummaryViewModel,
createProjectBatchAcceptanceReportValidationActionPlan,
createProjectBatchAcceptanceReportValidationSummaryViewModel,
createProjectBatchAcceptanceReportValidation,
createProjectBatchAcceptanceSummaryViewModel,
createProjectBatchAcceptanceWorkflow,
loadProjectBatchAcceptanceReportUrlWorkflow,
} from "../../../runtime/sdk/src/index.js";
const readyCapabilityMatrix = createProjectBatchAcceptanceCapabilityMatrix({
capabilities: [
{
id: "callable-api",
type: "api",
label: "Callable API",
evidence: "createProjectBatchAcceptanceCapabilityMatrix",
},
{
id: "browser-smoke",
type: "browser",
label: "Browser smoke",
evidence: "browser smoke output",
},
{
id: "docs-only-note",
type: "docs",
label: "Docs-only note",
},
],
});
assert.equal(readyCapabilityMatrix.apiName, "project-batch-acceptance-capability-matrix");
assert.equal(readyCapabilityMatrix.matrixVersion, 1);
assert.equal(readyCapabilityMatrix.phase, "ready");
assert.equal(readyCapabilityMatrix.ready, true);
assert.deepEqual(readyCapabilityMatrix.acceptedCapabilityTypes, ["api", "workflow", "gate", "browser"]);
assert.deepEqual(readyCapabilityMatrix.capabilityTypes, ["api", "browser"]);
assert.equal(readyCapabilityMatrix.capabilityCount, 2);
assert.equal(readyCapabilityMatrix.acceptedCount, 2);
assert.equal(readyCapabilityMatrix.rejectedCount, 1);
assert.deepEqual(readyCapabilityMatrix.countsByType, {
api: 1,
workflow: 0,
gate: 0,
browser: 1,
});
assert.equal(readyCapabilityMatrix.rejectedCapabilities[0].type, "docs");
assert.deepEqual(readyCapabilityMatrix.missing, []);
assert.deepEqual(
readyCapabilityMatrix.rows.map(({ id, value }) => [id, value]),
[
["accepted-capabilities", "2"],
["accepted-types", "api, browser"],
["rejected-capabilities", "1"],
["missing", "none"],
],
);
const blockedCapabilityMatrix = createProjectBatchAcceptanceCapabilityMatrix({
capabilities: [
{
id: "docs-only-note",
type: "docs",
label: "Docs-only note",
},
],
});
assert.equal(blockedCapabilityMatrix.phase, "blocked");
assert.equal(blockedCapabilityMatrix.ready, false);
assert.equal(blockedCapabilityMatrix.acceptedCount, 0);
assert.equal(blockedCapabilityMatrix.rejectedCount, 1);
assert.deepEqual(blockedCapabilityMatrix.missing, ["batch-capability"]);
const readyWorkflow = createProjectBatchAcceptanceWorkflow({
batchId: "batch-acceptance-workflow",
capabilities: [
{
id: "project-batch-acceptance-workflow-api",
type: "api",
label: "Project batch acceptance workflow API",
evidence: "createProjectBatchAcceptanceWorkflow",
},
{
id: "project-batch-acceptance-node-gate",
type: "gate",
label: "Project batch acceptance Node gate",
evidence: "project_batch_acceptance_workflow_node_smoke=ok",
command: "wasm-port/tests/sdk/node/verify_project_batch_acceptance_workflow.sh",
},
],
observedOutputs: ["project_batch_acceptance_workflow_node_smoke=ok"],
});
assert.equal(readyWorkflow.apiName, "project-batch-acceptance-workflow");
assert.equal(readyWorkflow.workflowVersion, 1);
assert.equal(readyWorkflow.phase, "ready");
assert.equal(readyWorkflow.ready, true);
assert.equal(readyWorkflow.batchId, "batch-acceptance-workflow");
assert.deepEqual(readyWorkflow.acceptedCapabilityTypes, ["api", "workflow", "gate", "browser"]);
assert.deepEqual(readyWorkflow.capabilityTypes, ["api", "gate"]);
assert.equal(readyWorkflow.capabilityCount, 2);
assert.equal(readyWorkflow.capabilityMatrix.apiName, "project-batch-acceptance-capability-matrix");
assert.equal(readyWorkflow.capabilityMatrix.ready, true);
assert.deepEqual(readyWorkflow.missing, []);
assert.equal(
readyWorkflow.gateResultMatrix.rows.find(({ id }) => id === "project-batch-acceptance")?.passed,
true,
);
assert.deepEqual(
readyWorkflow.rows.map(({ id, value }) => [id, value]),
[
["batch", "batch-acceptance-workflow"],
["capabilities", "api, gate"],
["project-batch-acceptance", "passed"],
["missing", "none"],
],
);
const readySummary = createProjectBatchAcceptanceSummaryViewModel(readyWorkflow);
assert.equal(readySummary.apiName, "project-batch-acceptance-summary-view-model");
assert.equal(readySummary.phase, "ready");
assert.equal(readySummary.statusLine, "Ready: Batch acceptance evidence complete");
assert.equal(readySummary.rows.find(({ id }) => id === "capability-count")?.value, "2");
const readyActionPlan = createProjectBatchAcceptanceActionPlan(readyWorkflow);
assert.equal(readyActionPlan.apiName, "project-batch-acceptance-action-plan");
assert.equal(readyActionPlan.phase, "ready");
assert.equal(readyActionPlan.ready, true);
assert.equal(readyActionPlan.commandCount, 0);
assert.equal(readyActionPlan.nextCommand, null);
assert.equal(readyActionPlan.shellScript, "# Project batch acceptance workflow is already ready.\n");
const readyChecklist = createProjectBatchAcceptanceChecklist({
workflow: readyWorkflow,
actionPlan: readyActionPlan,
});
assert.equal(readyChecklist.apiName, "project-batch-acceptance-checklist");
assert.equal(readyChecklist.checklistVersion, 1);
assert.equal(readyChecklist.phase, "ready");
assert.equal(readyChecklist.ready, true);
assert.equal(readyChecklist.batchId, "batch-acceptance-workflow");
assert.equal(readyChecklist.passedCount, 3);
assert.equal(readyChecklist.blockedCount, 0);
assert.deepEqual(
readyChecklist.rows.map(({ id, value }) => [id, value]),
[
["accepted-capability", "pass: api, gate"],
["batch-acceptance-gate", "pass: passed"],
["next-action", "pass: none"],
],
);
const readyReport = createProjectBatchAcceptanceReport({
batchId: "batch-acceptance-workflow",
capabilities: readyWorkflow.capabilities,
observedOutputs: ["project_batch_acceptance_workflow_node_smoke=ok"],
});
assert.equal(readyReport.apiName, "project-batch-acceptance-report");
assert.equal(readyReport.reportVersion, 1);
assert.equal(readyReport.phase, "ready");
assert.equal(readyReport.ready, true);
assert.equal(readyReport.batchId, "batch-acceptance-workflow");
assert.deepEqual(readyReport.missing, []);
assert.equal(readyReport.capabilityMatrix.apiName, "project-batch-acceptance-capability-matrix");
assert.equal(readyReport.workflow.apiName, "project-batch-acceptance-workflow");
assert.equal(readyReport.summaryViewModel.statusLine, "Ready: Batch acceptance evidence complete");
assert.equal(readyReport.actionPlan.nextCommand, null);
assert.equal(readyReport.checklist.passedCount, 3);
assert.deepEqual(
readyReport.rows.map(({ id, value }) => [id, value]),
[
["summary", "Ready: Batch acceptance evidence complete"],
["capabilities", "api, gate"],
["checklist", "3/3 passed"],
["next-command", "none"],
],
);
const readyReportValidation = createProjectBatchAcceptanceReportValidation(readyReport);
const readyReportValidationSummary = createProjectBatchAcceptanceReportValidationSummaryViewModel(
readyReportValidation,
);
const readyReportValidationActionPlan = createProjectBatchAcceptanceReportValidationActionPlan(
readyReportValidation,
);
assert.equal(readyReportValidation.apiName, "project-batch-acceptance-report-validation");
assert.equal(readyReportValidation.validationVersion, 1);
assert.equal(readyReportValidation.phase, "ready");
assert.equal(readyReportValidation.ready, true);
assert.deepEqual(readyReportValidation.missing, []);
assert.equal(readyReportValidation.batchId, "batch-acceptance-workflow");
assert.deepEqual(
readyReportValidation.rows.map(({ id, value }) => [id, value]),
[
["report", "project-batch-acceptance-report"],
["capability-matrix", "ready"],
["workflow", "ready"],
["checklist", "ready"],
["missing", "none"],
],
);
assert.equal(
readyReportValidationSummary.statusLine,
"Ready: Batch acceptance report evidence complete",
);
assert.deepEqual(
readyReportValidationSummary.rows.map(({ id, value }) => [id, value]),
[
["report", "project-batch-acceptance-report"],
["batch", "batch-acceptance-workflow"],
["validation", "ready"],
["missing", "none"],
],
);
assert.deepEqual(readyReportValidationActionPlan, {
apiName: "project-batch-acceptance-report-validation-action-plan",
planVersion: 1,
phase: "ready",
ready: true,
missing: [],
missingCount: 0,
commandCount: 0,
nextActionId: null,
nextCommand: null,
commands: [],
shellScript: "# Project batch acceptance report validation is already ready.\n",
rows: [
["report-validation", "Report validation", "ready"],
["missing", "Missing report evidence", "none"],
["next-command", "Next command", "none"],
].map(([id, label, value]) => ({ id, label, value })),
});
const readyReportJsonWorkflow = createProjectBatchAcceptanceReportJsonWorkflow({
reportJson: JSON.stringify(readyReport),
});
assert.equal(readyReportJsonWorkflow.apiName, "project-batch-acceptance-report-json-workflow");
assert.equal(readyReportJsonWorkflow.workflowVersion, 1);
assert.equal(readyReportJsonWorkflow.phase, "ready");
assert.equal(readyReportJsonWorkflow.ready, true);
assert.equal(readyReportJsonWorkflow.parsed, true);
assert.equal(readyReportJsonWorkflow.parseError, null);
assert.equal(readyReportJsonWorkflow.validation.ready, true);
assert.deepEqual(readyReportJsonWorkflow.missing, []);
let fetchedReportUrl = null;
const readyReportUrlWorkflow = await loadProjectBatchAcceptanceReportUrlWorkflow({
reportUrl: "/project-batch-acceptance.json",
fetchRef: async (url) => {
fetchedReportUrl = url;
return {
ok: true,
status: 200,
text: async () => JSON.stringify(readyReport),
};
},
});
const readyReportUrlSummary = createProjectBatchAcceptanceReportUrlWorkflowSummaryViewModel(
readyReportUrlWorkflow,
);
const readyReportUrlActionPlan = createProjectBatchAcceptanceReportUrlWorkflowActionPlan(
readyReportUrlWorkflow,
);
assert.equal(fetchedReportUrl, "/project-batch-acceptance.json");
assert.equal(readyReportUrlWorkflow.apiName, "project-batch-acceptance-report-url-workflow");
assert.equal(readyReportUrlWorkflow.workflowVersion, 1);
assert.equal(readyReportUrlWorkflow.phase, "ready");
assert.equal(readyReportUrlWorkflow.ready, true);
assert.equal(readyReportUrlWorkflow.fetched, true);
assert.equal(readyReportUrlWorkflow.httpStatus, 200);
assert.equal(readyReportUrlWorkflow.fetchError, null);
assert.equal(readyReportUrlWorkflow.jsonWorkflow.ready, true);
assert.equal(readyReportUrlWorkflow.validation.ready, true);
assert.deepEqual(readyReportUrlWorkflow.missing, []);
assert.equal(
readyReportUrlSummary.statusLine,
"Ready: Batch report URL workflow complete",
);
assert.deepEqual(
readyReportUrlSummary.rows.map(({ id, value }) => [id, value]),
[
["report-url", "/project-batch-acceptance.json"],
["fetch", "ready (200)"],
["json-workflow", "ready"],
["report-validation", "ready"],
["missing", "none"],
],
);
assert.deepEqual(readyReportUrlActionPlan, {
apiName: "project-batch-acceptance-report-url-workflow-action-plan",
planVersion: 1,
phase: "ready",
ready: true,
missing: [],
actionCount: 0,
commandCount: 0,
nextActionId: null,
nextCommand: null,
actions: [],
commands: [],
shellScript: "# Project batch acceptance report URL workflow is already ready.\n",
rows: [],
});
const blockedWorkflow = createProjectBatchAcceptanceWorkflow({
batchId: "docs-only-batch",
capabilities: [
{
id: "docs-only-note",
type: "docs",
label: "Docs-only note",
},
],
});
assert.equal(blockedWorkflow.phase, "blocked");
assert.equal(blockedWorkflow.ready, false);
assert.deepEqual(blockedWorkflow.capabilityTypes, []);
assert.equal(blockedWorkflow.rejectedCapabilities[0].type, "docs");
assert.deepEqual(blockedWorkflow.missing, ["batch-capability", "project-batch-acceptance"]);
const blockedSummary = createProjectBatchAcceptanceSummaryViewModel(blockedWorkflow);
assert.equal(blockedSummary.statusLine, "Blocked: batch-capability, project-batch-acceptance");
const blockedActionPlan = createProjectBatchAcceptanceActionPlan(blockedWorkflow);
assert.equal(blockedActionPlan.phase, "blocked");
assert.equal(blockedActionPlan.ready, false);
assert.equal(blockedActionPlan.commandCount, 1);
assert.equal(blockedActionPlan.nextActionId, "project-batch-acceptance");
assert.equal(
blockedActionPlan.nextCommand,
"wasm-port/tests/sdk/node/verify_project_batch_acceptance_workflow.sh",
);
assert.match(blockedActionPlan.shellScript, /verify_project_batch_acceptance_workflow\.sh/);
const blockedChecklist = createProjectBatchAcceptanceChecklist({
workflow: blockedWorkflow,
actionPlan: blockedActionPlan,
});
assert.equal(blockedChecklist.phase, "blocked");
assert.equal(blockedChecklist.ready, false);
assert.equal(blockedChecklist.passedCount, 0);
assert.equal(blockedChecklist.blockedCount, 3);
assert.deepEqual(
blockedChecklist.items.map(({ id, status }) => [id, status]),
[
["accepted-capability", "blocked"],
["batch-acceptance-gate", "blocked"],
["next-action", "blocked"],
],
);
const blockedReport = createProjectBatchAcceptanceReport({
batchId: "docs-only-batch",
capabilities: blockedWorkflow.rejectedCapabilities,
});
assert.equal(blockedReport.phase, "blocked");
assert.equal(blockedReport.ready, false);
assert.deepEqual(blockedReport.missing, ["batch-capability", "project-batch-acceptance"]);
assert.equal(blockedReport.checklist.blockedCount, 3);
assert.equal(
blockedReport.rows.find(({ id }) => id === "next-command")?.value,
"wasm-port/tests/sdk/node/verify_project_batch_acceptance_workflow.sh",
);
const blockedReportValidation = createProjectBatchAcceptanceReportValidation(blockedReport);
const blockedReportValidationSummary = createProjectBatchAcceptanceReportValidationSummaryViewModel(
blockedReportValidation,
);
const blockedReportValidationActionPlan = createProjectBatchAcceptanceReportValidationActionPlan(
blockedReportValidation,
);
assert.equal(blockedReportValidation.phase, "blocked");
assert.equal(blockedReportValidation.ready, false);
assert.deepEqual(blockedReportValidation.missing, [
"phase",
"ready",
"missing",
"capabilityMatrix",
"workflow",
"summaryViewModel",
"actionPlan",
"checklist",
"rows.checklist",
]);
assert.match(blockedReportValidationSummary.statusLine, /^Blocked: phase, ready, missing/);
assert.equal(blockedReportValidationSummary.rows.find(({ id }) => id === "validation")?.value, "blocked");
assert.equal(blockedReportValidationActionPlan.phase, "blocked");
assert.equal(blockedReportValidationActionPlan.ready, false);
assert.equal(blockedReportValidationActionPlan.commandCount, 1);
assert.equal(
blockedReportValidationActionPlan.nextCommand,
"wasm-port/tests/sdk/node/verify_project_batch_acceptance_workflow.sh",
);
assert.equal(
blockedReportValidationActionPlan.commands[0].expectedOutput,
"project_batch_acceptance_artifact_node_smoke=ok",
);
const invalidReportJsonWorkflow = createProjectBatchAcceptanceReportJsonWorkflow({
reportJson: "{not-json",
});
assert.equal(invalidReportJsonWorkflow.phase, "blocked");
assert.equal(invalidReportJsonWorkflow.ready, false);
assert.equal(invalidReportJsonWorkflow.parsed, false);
assert.match(invalidReportJsonWorkflow.parseError, /JSON/);
assert.equal(invalidReportJsonWorkflow.missing[0], "report-json");
const missingInputReportUrlWorkflow = await loadProjectBatchAcceptanceReportUrlWorkflow({
reportUrl: "",
fetchRef: null,
});
const missingInputReportUrlActionPlan = createProjectBatchAcceptanceReportUrlWorkflowActionPlan(
missingInputReportUrlWorkflow,
);
assert.equal(missingInputReportUrlWorkflow.ready, false);
assert.equal(missingInputReportUrlWorkflow.fetched, false);
assert.deepEqual(missingInputReportUrlWorkflow.missing.slice(0, 2), ["report-url", "fetch"]);
assert.match(
createProjectBatchAcceptanceReportUrlWorkflowSummaryViewModel(
missingInputReportUrlWorkflow,
).statusLine,
/^Blocked: report-url, fetch/,
);
assert.equal(missingInputReportUrlActionPlan.actionCount, 3);
assert.equal(missingInputReportUrlActionPlan.commandCount, 1);
assert.equal(missingInputReportUrlActionPlan.nextActionId, "provide-report-url");
assert.equal(
missingInputReportUrlActionPlan.nextCommand,
"wasm-port/tests/sdk/node/verify_project_batch_acceptance_workflow.sh",
);
assert.deepEqual(
missingInputReportUrlActionPlan.actions.slice(0, 2).map(({ id, kind, command }) => [id, kind, command]),
[
["provide-report-url", "input", null],
["provide-fetch", "input", null],
],
);
const httpFailureReportUrlWorkflow = await loadProjectBatchAcceptanceReportUrlWorkflow({
reportUrl: "/missing-batch-report.json",
fetchRef: async () => ({
ok: false,
status: 404,
text: async () => "",
}),
});
const httpFailureReportUrlActionPlan = createProjectBatchAcceptanceReportUrlWorkflowActionPlan(
httpFailureReportUrlWorkflow,
);
assert.equal(httpFailureReportUrlWorkflow.ready, false);
assert.equal(httpFailureReportUrlWorkflow.fetched, false);
assert.equal(httpFailureReportUrlWorkflow.httpStatus, 404);
assert.match(httpFailureReportUrlWorkflow.fetchError, /404/);
assert.equal(httpFailureReportUrlWorkflow.missing[0], "report-fetch");
assert.match(
createProjectBatchAcceptanceReportUrlWorkflowSummaryViewModel(
httpFailureReportUrlWorkflow,
).statusLine,
/^Blocked: report-fetch/,
);
assert.equal(httpFailureReportUrlActionPlan.nextActionId, "verify-report-url-fetch");
assert.equal(httpFailureReportUrlActionPlan.commands.length, 1);
const thrownFetchReportUrlWorkflow = await loadProjectBatchAcceptanceReportUrlWorkflow({
reportUrl: "/throws-batch-report.json",
fetchRef: async () => {
throw new Error("batch report network unavailable");
},
});
assert.equal(thrownFetchReportUrlWorkflow.ready, false);
assert.equal(thrownFetchReportUrlWorkflow.fetched, false);
assert.equal(thrownFetchReportUrlWorkflow.httpStatus, null);
assert.equal(thrownFetchReportUrlWorkflow.fetchError, "batch report network unavailable");
assert.equal(thrownFetchReportUrlWorkflow.missing[0], "report-fetch");
assert.equal(
createProjectBatchAcceptanceReportUrlWorkflowActionPlan(
thrownFetchReportUrlWorkflow,
).nextActionId,
"verify-report-url-fetch",
);
const capabilityOnlyWorkflow = createProjectBatchAcceptanceWorkflow({
capabilities: [
{
id: "callable-helper",
type: "api",
label: "Callable helper",
},
],
});
assert.deepEqual(capabilityOnlyWorkflow.missing, ["project-batch-acceptance"]);
assert.equal(
createProjectBatchAcceptanceActionPlan(capabilityOnlyWorkflow).nextCommand,
"wasm-port/tests/sdk/node/verify_project_batch_acceptance_workflow.sh",
);
console.log("project_batch_acceptance_workflow_node_smoke=ok");

View File

@@ -0,0 +1,9 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "$0")/../../.." && pwd)"
ARTIFACT_PATH="${1:-$ROOT_DIR/build/project-batch-acceptance.json}"
node "$ROOT_DIR/tests/sdk/node/verify_project_batch_acceptance_workflow.mjs"
node "$ROOT_DIR/tests/sdk/node/write_project_batch_acceptance_artifact.mjs" "$ARTIFACT_PATH"
"$ROOT_DIR/tests/sdk/node/verify_project_batch_acceptance_artifact.sh" "$ARTIFACT_PATH"

View File

@@ -0,0 +1,157 @@
import assert from "node:assert/strict";
import {
createProjectReleaseGateManifest,
createProjectReleaseReadinessArtifactUrlWorkflowActionPlan,
createProjectReleaseReadinessArtifactUrlWorkflowSummaryViewModel,
createProjectReleaseReadinessReport,
loadProjectReleaseReadinessArtifactUrlWorkflow,
} from "../../../runtime/sdk/src/index.js";
const manifest = createProjectReleaseGateManifest();
const readyArtifactJson = JSON.stringify(createProjectReleaseReadinessReport({
gateResults: Object.fromEntries(manifest.gateIds.map((id) => [id, true])),
}));
let fetchedUrl = null;
const readyWorkflow = await loadProjectReleaseReadinessArtifactUrlWorkflow({
artifactUrl: "/project-release-readiness.json",
fetchRef: async (url) => {
fetchedUrl = url;
return {
ok: true,
status: 200,
text: async () => readyArtifactJson,
};
},
});
const readySummary = createProjectReleaseReadinessArtifactUrlWorkflowSummaryViewModel(readyWorkflow);
const readyActionPlan = createProjectReleaseReadinessArtifactUrlWorkflowActionPlan(readyWorkflow);
assert.equal(fetchedUrl, "/project-release-readiness.json");
assert.equal(readyWorkflow.apiName, "project-release-readiness-artifact-url-workflow");
assert.equal(readyWorkflow.workflowVersion, 1);
assert.equal(readyWorkflow.ready, true);
assert.equal(readyWorkflow.fetched, true);
assert.equal(readyWorkflow.httpStatus, 200);
assert.equal(readyWorkflow.fetchError, null);
assert.equal(readyWorkflow.jsonWorkflow.ready, true);
assert.equal(readyWorkflow.validation.ready, true);
assert.equal(readyWorkflow.actionPlan.ready, true);
assert.equal(readySummary.apiName, "project-release-readiness-artifact-url-workflow-summary-view-model");
assert.equal(readySummary.statusLine, "Ready: Artifact URL workflow complete");
assert.deepEqual(
readySummary.rows.map(({ id, value }) => [id, value]),
[
["artifact-url", "/project-release-readiness.json"],
["fetch", "ready (200)"],
["json-workflow", "ready"],
["artifact-validation", "ready"],
["next-command", "none"],
["missing", "none"],
],
);
assert.deepEqual(readyActionPlan, {
apiName: "project-release-readiness-artifact-url-workflow-action-plan",
planVersion: 1,
phase: "ready",
ready: true,
missing: [],
actionCount: 0,
commandCount: 0,
nextActionId: null,
nextCommand: null,
actions: [],
commands: [],
shellScript: "# Project release readiness artifact URL workflow is already ready.\n",
rows: [],
});
const missingInputWorkflow = await loadProjectReleaseReadinessArtifactUrlWorkflow({
artifactUrl: "",
fetchRef: null,
});
const missingInputSummary = createProjectReleaseReadinessArtifactUrlWorkflowSummaryViewModel(
missingInputWorkflow,
);
const missingInputActionPlan = createProjectReleaseReadinessArtifactUrlWorkflowActionPlan(
missingInputWorkflow,
);
assert.equal(missingInputWorkflow.ready, false);
assert.equal(missingInputWorkflow.fetched, false);
assert.deepEqual(missingInputWorkflow.missing.slice(0, 2), ["artifact-url", "fetch"]);
assert.match(missingInputSummary.statusLine, /^Blocked: artifact-url, fetch/);
assert.equal(missingInputSummary.rows.find(({ id }) => id === "artifact-url")?.value, "missing");
assert.equal(missingInputActionPlan.phase, "blocked");
assert.equal(missingInputActionPlan.actionCount, 4);
assert.equal(missingInputActionPlan.commandCount, 2);
assert.equal(missingInputActionPlan.nextActionId, "provide-artifact-url");
assert.equal(
missingInputActionPlan.nextCommand,
"wasm-port/tests/host/verify_project_release_gate.sh",
);
assert.deepEqual(
missingInputActionPlan.actions.slice(0, 2).map(({ id, kind, command }) => [id, kind, command]),
[
["provide-artifact-url", "input", null],
["provide-fetch", "input", null],
],
);
assert.match(missingInputActionPlan.shellScript, /^#!\/usr\/bin\/env bash\nset -euo pipefail\n/);
const httpFailureWorkflow = await loadProjectReleaseReadinessArtifactUrlWorkflow({
artifactUrl: "/missing.json",
fetchRef: async () => ({
ok: false,
status: 404,
text: async () => "",
}),
});
const httpFailureSummary = createProjectReleaseReadinessArtifactUrlWorkflowSummaryViewModel(
httpFailureWorkflow,
);
const httpFailureActionPlan = createProjectReleaseReadinessArtifactUrlWorkflowActionPlan(
httpFailureWorkflow,
);
assert.equal(httpFailureWorkflow.ready, false);
assert.equal(httpFailureWorkflow.fetched, false);
assert.equal(httpFailureWorkflow.httpStatus, 404);
assert.match(httpFailureWorkflow.fetchError, /404/);
assert.equal(httpFailureWorkflow.missing[0], "artifact-fetch");
assert.equal(
httpFailureWorkflow.actionPlan.nextCommand,
"wasm-port/tests/host/verify_project_release_gate.sh",
);
assert.match(httpFailureSummary.statusLine, /^Blocked: artifact-fetch/);
assert.equal(httpFailureSummary.rows.find(({ id }) => id === "fetch")?.value, "blocked");
assert.equal(httpFailureActionPlan.phase, "blocked");
assert.equal(httpFailureActionPlan.actionCount, 3);
assert.equal(httpFailureActionPlan.commandCount, 2);
assert.equal(httpFailureActionPlan.nextActionId, "verify-artifact-url-fetch");
assert.equal(
httpFailureActionPlan.rows[0].value,
"fetch",
);
const thrownFetchWorkflow = await loadProjectReleaseReadinessArtifactUrlWorkflow({
artifactUrl: "/throws.json",
fetchRef: async () => {
throw new Error("network unavailable");
},
});
const thrownFetchSummary = createProjectReleaseReadinessArtifactUrlWorkflowSummaryViewModel(
thrownFetchWorkflow,
);
const thrownFetchActionPlan = createProjectReleaseReadinessArtifactUrlWorkflowActionPlan(
thrownFetchWorkflow,
);
assert.equal(thrownFetchWorkflow.ready, false);
assert.equal(thrownFetchWorkflow.fetched, false);
assert.equal(thrownFetchWorkflow.httpStatus, null);
assert.equal(thrownFetchWorkflow.fetchError, "network unavailable");
assert.equal(thrownFetchWorkflow.missing[0], "artifact-fetch");
assert.match(thrownFetchSummary.statusLine, /^Blocked: artifact-fetch/);
assert.equal(thrownFetchActionPlan.nextActionId, "verify-artifact-url-fetch");
assert.equal(thrownFetchActionPlan.commands.length, 2);
console.log("project_release_artifact_url_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/sdk/node/verify_project_release_artifact_url_workflow.mjs"

View File

@@ -0,0 +1,501 @@
import assert from "node:assert/strict";
import {
createProjectReleaseGateActionPlan,
createProjectReleaseGateExecutionManifest,
createProjectReleaseGateExecutionSummaryViewModel,
createProjectReleaseGateManifest,
createProjectReleaseGateResultMatrix,
createProjectReleaseReadinessArtifactJsonWorkflow,
createProjectReleaseReadinessArtifactUrlWorkflowSummaryViewModel,
createProjectReleaseReadinessArtifactValidation,
createProjectReleaseReadinessArtifactValidationActionPlan,
createProjectReleaseReadinessArtifactValidationSummaryViewModel,
createProjectReleaseReadinessReport,
createProjectReleaseReadinessSummaryViewModel,
loadProjectReleaseReadinessArtifactUrlWorkflow,
} from "../../../runtime/sdk/src/index.js";
const manifest = createProjectReleaseGateManifest();
assert.equal(manifest.apiName, "project-release-gate-manifest");
assert.equal(manifest.manifestVersion, 1);
assert.equal(manifest.gateCount, 12);
assert.deepEqual(manifest.gateIds, [
"diff-check",
"vendor-sync",
"standalone-cnc-semantics",
"interp-wasm",
"sim-config-inventory",
"ini-panel-browser",
"opfs-session-browser",
"release-artifact-url-browser",
"project-batch-acceptance",
"ui-node-smokes",
"host-smokes",
"project-release-gate",
]);
assert.deepEqual(
manifest.gates.map(({ expectedOutput }) => expectedOutput),
[
null,
"vendor sync up to date",
"standalone CNC semantics guard complete",
"interp_wasm_node_smoke=ok",
"sim_configs_wasm_node_inventory_unexpected_fail=0",
"browser_ini_shell_integration_workflow_smoke=ok",
"browser_opfs_session_workflow_smoke=ok",
"browser_release_artifact_url_workflow_smoke=ok",
"project_batch_acceptance_workflow_node_smoke=ok",
"ui_node_smokes=ok",
"host_wasm_opfs_browser_smokes=ok",
"project_release_gate=ok",
],
);
assert.equal(
manifest.gates.find(({ id }) => id === "release-artifact-url-browser")?.command,
"SKIP_INI_BUILD=1 SKIP_INTERP_BUILD=1 wasm-port/tests/browser/verify_release_artifact_url_workflow_browser.sh",
);
assert.equal(
manifest.gates.find(({ id }) => id === "opfs-session-browser")?.command,
"SKIP_INI_BUILD=1 SKIP_INTERP_BUILD=1 wasm-port/tests/browser/verify_opfs_session_workflow_browser.sh",
);
assert.equal(
manifest.gates.find(({ id }) => id === "project-batch-acceptance")?.command,
"wasm-port/tests/sdk/node/verify_project_batch_acceptance_workflow.sh",
);
const partialMatrix = createProjectReleaseGateResultMatrix({
gateResults: {
"diff-check": true,
},
observedOutputs: [
"vendor sync up to date",
"browser_release_artifact_url_workflow_smoke=ok",
],
});
assert.equal(partialMatrix.apiName, "project-release-gate-result-matrix");
assert.equal(partialMatrix.matrixVersion, 1);
assert.equal(partialMatrix.phase, "waiting");
assert.equal(partialMatrix.ready, false);
assert.equal(partialMatrix.gateCount, 12);
assert.equal(partialMatrix.passedCount, 3);
assert.equal(partialMatrix.unknownCount, 9);
assert.deepEqual(partialMatrix.missingGateIds, [
"standalone-cnc-semantics",
"interp-wasm",
"sim-config-inventory",
"ini-panel-browser",
"opfs-session-browser",
"project-batch-acceptance",
"ui-node-smokes",
"host-smokes",
"project-release-gate",
]);
assert.deepEqual(
partialMatrix.rows.filter(({ passed }) => passed).map(({ id }) => id),
[
"diff-check",
"vendor-sync",
"release-artifact-url-browser",
],
);
const partialExecutionManifest = createProjectReleaseGateExecutionManifest({
gateResults: {
"diff-check": true,
},
observedOutputs: [
"vendor sync up to date",
"browser_release_artifact_url_workflow_smoke=ok",
],
});
assert.equal(partialExecutionManifest.apiName, "project-release-gate-execution-manifest");
assert.equal(partialExecutionManifest.manifestVersion, 1);
assert.equal(partialExecutionManifest.phase, "waiting");
assert.equal(partialExecutionManifest.ready, false);
assert.equal(partialExecutionManifest.gateCount, 12);
assert.equal(partialExecutionManifest.passedCount, 3);
assert.equal(partialExecutionManifest.unknownCount, 9);
assert.deepEqual(partialExecutionManifest.missingGateIds, partialMatrix.missingGateIds);
assert.deepEqual(
partialExecutionManifest.rows.filter(({ passed }) => passed).map(({ id, evidence }) => [id, evidence]),
[
["diff-check", "explicit-result"],
["vendor-sync", "expected-output"],
["release-artifact-url-browser", "expected-output"],
],
);
assert.equal(
partialExecutionManifest.rows.find(({ id }) => id === "vendor-sync")?.observedOutput,
"vendor sync up to date",
);
assert.equal(
partialExecutionManifest.rows.find(({ id }) => id === "standalone-cnc-semantics")?.observedOutput,
null,
);
const partialExecutionSummary = createProjectReleaseGateExecutionSummaryViewModel(partialExecutionManifest);
assert.equal(partialExecutionSummary.apiName, "project-release-gate-execution-summary-view-model");
assert.equal(partialExecutionSummary.viewModelVersion, 1);
assert.equal(partialExecutionSummary.phase, "waiting");
assert.equal(partialExecutionSummary.ready, false);
assert.equal(partialExecutionSummary.statusLine, "Waiting: standalone-cnc-semantics, interp-wasm, sim-config-inventory, ini-panel-browser, opfs-session-browser, project-batch-acceptance, ui-node-smokes, host-smokes, project-release-gate");
assert.equal(partialExecutionSummary.gateCount, 12);
assert.equal(partialExecutionSummary.passedCount, 3);
assert.equal(partialExecutionSummary.unknownCount, 9);
assert.equal(partialExecutionSummary.nextGateId, "standalone-cnc-semantics");
assert.deepEqual(partialExecutionSummary.evidenceCounts, {
"explicit-result": 1,
"expected-output": 2,
missing: 9,
});
assert.deepEqual(
partialExecutionSummary.rows.map(({ id, value }) => [id, value]),
[
["gate-results", "3/12 passed"],
["unknown-gates", "9"],
["next-gate", "standalone-cnc-semantics"],
["expected-output-evidence", "2"],
["explicit-result-evidence", "1"],
["missing", "standalone-cnc-semantics, interp-wasm, sim-config-inventory, ini-panel-browser, opfs-session-browser, project-batch-acceptance, ui-node-smokes, host-smokes, project-release-gate"],
],
);
assert.equal(partialExecutionSummary.gateRows.length, manifest.gateCount);
assert.equal(
partialExecutionSummary.gateRows.find(({ id }) => id === "vendor-sync")?.evidence,
"expected-output",
);
const partialActionPlan = createProjectReleaseGateActionPlan({ gateResultMatrix: partialMatrix });
assert.equal(partialActionPlan.apiName, "project-release-gate-action-plan");
assert.equal(partialActionPlan.planVersion, 1);
assert.equal(partialActionPlan.phase, "waiting");
assert.equal(partialActionPlan.ready, false);
assert.equal(partialActionPlan.gateCount, 12);
assert.equal(partialActionPlan.completedCount, 3);
assert.equal(partialActionPlan.pendingCount, 9);
assert.deepEqual(partialActionPlan.completedGateIds, [
"diff-check",
"vendor-sync",
"release-artifact-url-browser",
]);
assert.deepEqual(partialActionPlan.pendingGateIds, [
"standalone-cnc-semantics",
"interp-wasm",
"sim-config-inventory",
"ini-panel-browser",
"opfs-session-browser",
"project-batch-acceptance",
"ui-node-smokes",
"host-smokes",
"project-release-gate",
]);
assert.equal(partialActionPlan.nextGateId, "standalone-cnc-semantics");
assert.equal(partialActionPlan.nextCommand, "wasm-port/tools/verify_no_standalone_cnc_semantics.sh");
assert.equal(partialActionPlan.commands.length, 9);
assert.equal(partialActionPlan.commands[0].step, 1);
assert.equal(partialActionPlan.commands[0].gateId, "standalone-cnc-semantics");
assert.equal(partialActionPlan.rows[0].id, "standalone-cnc-semantics");
assert.match(partialActionPlan.shellScript, /^#!\/usr\/bin\/env bash\nset -euo pipefail\n/);
assert.match(partialActionPlan.shellScript, /wasm-port\/tests\/host\/verify_project_release_gate\.sh/);
const report = createProjectReleaseReadinessReport({
gateResults: Object.fromEntries(manifest.gateIds.map((id) => [id, true])),
});
assert.deepEqual(report.gateManifest, manifest);
assert.equal(report.gateExecutionManifest.apiName, "project-release-gate-execution-manifest");
assert.equal(report.gateExecutionManifest.ready, true);
assert.equal(report.gateExecutionManifest.passedCount, manifest.gateCount);
assert.equal(report.gateExecutionSummaryViewModel.apiName, "project-release-gate-execution-summary-view-model");
assert.equal(report.gateExecutionSummaryViewModel.statusLine, "Ready: All release gate evidence present");
assert.equal(report.gateExecutionSummaryViewModel.rows[0].value, "12/12 passed");
assert.equal(report.gateExecutionSummaryViewModel.nextGateId, null);
assert.equal(report.gateResultMatrix.ready, true);
assert.equal(report.gateResultMatrix.passedCount, manifest.gateCount);
assert.equal(report.gateActionPlan.ready, true);
assert.equal(report.gateActionPlan.pendingCount, 0);
assert.equal(report.gateActionPlan.nextCommand, null);
assert.equal(report.gateActionPlan.shellScript, "# Project release gates are already marked passed.\n");
assert.deepEqual(report.gates.map(({ id }) => id), manifest.gateIds);
assert.equal(report.gates.length, manifest.gateCount);
assert.deepEqual(createProjectReleaseReadinessSummaryViewModel(report), {
apiName: "project-release-readiness-summary-view-model",
viewModelVersion: 1,
phase: "ready",
ready: true,
title: "Project release readiness",
statusText: "Ready",
detailText: "No blocking reasons",
statusLine: "Ready: No blocking reasons",
rows: [
{
id: "gate-results",
label: "Release gates",
value: "12/12 passed",
},
{
id: "unknown-gates",
label: "Unknown gates",
value: "0",
},
{
id: "sim-config-inventory",
label: "Sim config inventory",
value: "unexpected_fail=0",
},
{
id: "blocked-runtime-families",
label: "Blocked runtime families",
value: "L4-USER-M-PROCESS, L4-TOOL-DB, L4-PYTHON-REMAP",
},
{
id: "missing",
label: "Missing readiness",
value: "none",
},
],
});
assert.equal(
createProjectReleaseReadinessSummaryViewModel(createProjectReleaseReadinessReport()).statusLine,
"Waiting: project-release-gate",
);
assert.equal(
createProjectReleaseReadinessSummaryViewModel(createProjectReleaseReadinessReport({
gateResults: Object.fromEntries(manifest.gateIds.map((id) => [id, true])),
promotedRuntimeFamilies: ["L4-TOOL-DB"],
})).rows.find(({ id }) => id === "blocked-runtime-families")?.value,
"promoted: L4-TOOL-DB",
);
const artifactValidationSummary = createProjectReleaseReadinessArtifactValidationSummaryViewModel(
createProjectReleaseReadinessArtifactValidation(report),
);
const artifactValidationActionPlan = createProjectReleaseReadinessArtifactValidationActionPlan(
createProjectReleaseReadinessArtifactValidation(report),
);
const artifactJsonWorkflow = createProjectReleaseReadinessArtifactJsonWorkflow({
artifactJson: JSON.stringify(report),
});
const artifactUrlWorkflow = await loadProjectReleaseReadinessArtifactUrlWorkflow({
artifactUrl: "/project-release-readiness.json",
fetchRef: async () => ({
ok: true,
status: 200,
text: async () => JSON.stringify(report),
}),
});
const artifactUrlWorkflowSummary = createProjectReleaseReadinessArtifactUrlWorkflowSummaryViewModel(
artifactUrlWorkflow,
);
assert.equal(
artifactValidationSummary.apiName,
"project-release-readiness-artifact-validation-summary-view-model",
);
assert.equal(artifactValidationSummary.phase, "ready");
assert.equal(artifactValidationSummary.statusLine, "Ready: Artifact evidence complete");
assert.deepEqual(
artifactValidationSummary.rows.map(({ id, value }) => [id, value]),
[
["artifact", "project-release-readiness-report"],
["gate-count", "12/12"],
["gate-manifest", "ready"],
["gate-execution-manifest", "ready"],
["gate-execution-summary", "ready"],
["gate-result-matrix", "ready"],
["gate-action-plan", "ready"],
["missing", "none"],
],
);
assert.deepEqual(artifactValidationActionPlan, {
apiName: "project-release-readiness-artifact-validation-action-plan",
planVersion: 1,
phase: "ready",
ready: true,
missing: [],
missingCount: 0,
commandCount: 0,
nextActionId: null,
nextCommand: null,
commands: [],
shellScript: "# Project release readiness artifact validation is already ready.\n",
rows: [
{
id: "artifact-validation",
label: "Artifact validation",
value: "ready",
},
{
id: "missing",
label: "Missing artifact evidence",
value: "none",
},
{
id: "next-command",
label: "Next command",
value: "none",
},
],
});
assert.equal(artifactJsonWorkflow.apiName, "project-release-readiness-artifact-json-workflow");
assert.equal(artifactJsonWorkflow.workflowVersion, 1);
assert.equal(artifactJsonWorkflow.phase, "ready");
assert.equal(artifactJsonWorkflow.ready, true);
assert.equal(artifactJsonWorkflow.parsed, true);
assert.equal(artifactJsonWorkflow.parseError, null);
assert.equal(artifactJsonWorkflow.artifactApiName, "project-release-readiness-report");
assert.equal(artifactJsonWorkflow.validation.ready, true);
assert.equal(artifactJsonWorkflow.summaryViewModel.statusLine, "Ready: Artifact evidence complete");
assert.equal(artifactJsonWorkflow.actionPlan.commandCount, 0);
assert.deepEqual(
artifactJsonWorkflow.rows.map(({ id, value }) => [id, value]),
[
["parse", "ready"],
["validation", "ready"],
["next-command", "none"],
],
);
assert.equal(artifactUrlWorkflow.apiName, "project-release-readiness-artifact-url-workflow");
assert.equal(artifactUrlWorkflow.workflowVersion, 1);
assert.equal(artifactUrlWorkflow.phase, "ready");
assert.equal(artifactUrlWorkflow.ready, true);
assert.equal(artifactUrlWorkflow.artifactUrl, "/project-release-readiness.json");
assert.equal(artifactUrlWorkflow.fetched, true);
assert.equal(artifactUrlWorkflow.httpStatus, 200);
assert.equal(artifactUrlWorkflow.fetchError, null);
assert.equal(artifactUrlWorkflow.jsonWorkflow.ready, true);
assert.equal(artifactUrlWorkflow.validation.ready, true);
assert.equal(artifactUrlWorkflow.summaryViewModel.statusLine, "Ready: Artifact evidence complete");
assert.equal(artifactUrlWorkflow.actionPlan.commandCount, 0);
assert.deepEqual(
artifactUrlWorkflow.rows.map(({ id, value }) => [id, value]),
[
["fetch", "ready"],
["json-workflow", "ready"],
["next-command", "none"],
],
);
assert.equal(
artifactUrlWorkflowSummary.apiName,
"project-release-readiness-artifact-url-workflow-summary-view-model",
);
assert.equal(artifactUrlWorkflowSummary.viewModelVersion, 1);
assert.equal(artifactUrlWorkflowSummary.phase, "ready");
assert.equal(artifactUrlWorkflowSummary.statusLine, "Ready: Artifact URL workflow complete");
assert.deepEqual(
artifactUrlWorkflowSummary.rows.map(({ id, value }) => [id, value]),
[
["artifact-url", "/project-release-readiness.json"],
["fetch", "ready (200)"],
["json-workflow", "ready"],
["artifact-validation", "ready"],
["next-command", "none"],
["missing", "none"],
],
);
const staleArtifactValidationSummary = createProjectReleaseReadinessArtifactValidationSummaryViewModel(
createProjectReleaseReadinessArtifactValidation({}),
);
const staleArtifactValidation = createProjectReleaseReadinessArtifactValidation({});
const staleArtifactValidationActionPlan = createProjectReleaseReadinessArtifactValidationActionPlan(staleArtifactValidation);
const staleArtifactJsonWorkflow = createProjectReleaseReadinessArtifactJsonWorkflow({
artifactJson: "{}",
});
const invalidArtifactJsonWorkflow = createProjectReleaseReadinessArtifactJsonWorkflow({
artifactJson: "{not-json",
});
const missingArtifactUrlWorkflow = await loadProjectReleaseReadinessArtifactUrlWorkflow({
artifactUrl: "",
fetchRef: null,
});
const failedArtifactUrlWorkflow = await loadProjectReleaseReadinessArtifactUrlWorkflow({
artifactUrl: "/missing.json",
fetchRef: async () => ({
ok: false,
status: 404,
text: async () => "",
}),
});
const missingArtifactUrlWorkflowSummary = createProjectReleaseReadinessArtifactUrlWorkflowSummaryViewModel(
missingArtifactUrlWorkflow,
);
const failedArtifactUrlWorkflowSummary = createProjectReleaseReadinessArtifactUrlWorkflowSummaryViewModel(
failedArtifactUrlWorkflow,
);
assert.equal(staleArtifactValidationSummary.phase, "blocked");
assert.match(staleArtifactValidationSummary.statusLine, /^Blocked: apiName, reportVersion/);
assert.equal(staleArtifactValidationSummary.rows.find(({ id }) => id === "gate-action-plan")?.value, "missing");
assert.equal(
staleArtifactValidationSummary.rows.find(({ id }) => id === "gate-execution-manifest")?.value,
"missing",
);
assert.equal(
staleArtifactValidationSummary.rows.find(({ id }) => id === "gate-execution-summary")?.value,
"missing",
);
assert.equal(staleArtifactValidationActionPlan.phase, "blocked");
assert.equal(staleArtifactValidationActionPlan.ready, false);
assert.equal(staleArtifactValidationActionPlan.missingCount, staleArtifactValidation.missing.length);
assert.equal(staleArtifactValidationActionPlan.commandCount, 2);
assert.equal(staleArtifactValidationActionPlan.nextActionId, "project-release-gate");
assert.equal(
staleArtifactValidationActionPlan.nextCommand,
"wasm-port/tests/host/verify_project_release_gate.sh",
);
assert.equal(
staleArtifactValidationActionPlan.commands[1].command,
"wasm-port/tests/host/verify_project_release_readiness_artifact.sh build/project-release-readiness.json",
);
assert.match(staleArtifactValidationActionPlan.shellScript, /^#!\/usr\/bin\/env bash\nset -euo pipefail\n/);
assert.match(staleArtifactValidationActionPlan.shellScript, /verify_project_release_readiness_artifact\.sh/);
assert.equal(
staleArtifactValidationActionPlan.rows.find(({ id }) => id === "missing")?.value,
staleArtifactValidation.missing.join(", "),
);
assert.equal(staleArtifactJsonWorkflow.parsed, true);
assert.equal(staleArtifactJsonWorkflow.ready, false);
assert.equal(staleArtifactJsonWorkflow.phase, "blocked");
assert.equal(staleArtifactJsonWorkflow.actionPlan.commandCount, 2);
assert.deepEqual(staleArtifactJsonWorkflow.missing, staleArtifactValidation.missing);
assert.equal(invalidArtifactJsonWorkflow.parsed, false);
assert.equal(invalidArtifactJsonWorkflow.ready, false);
assert.equal(invalidArtifactJsonWorkflow.phase, "blocked");
assert.match(invalidArtifactJsonWorkflow.parseError, /JSON/);
assert.equal(invalidArtifactJsonWorkflow.missing[0], "artifact-json");
assert.equal(
invalidArtifactJsonWorkflow.actionPlan.nextCommand,
"wasm-port/tests/host/verify_project_release_gate.sh",
);
assert.deepEqual(
invalidArtifactJsonWorkflow.rows.map(({ id, value }) => [id, value]),
[
["parse", "blocked"],
["validation", "blocked"],
["next-command", "wasm-port/tests/host/verify_project_release_gate.sh"],
],
);
assert.equal(missingArtifactUrlWorkflow.ready, false);
assert.equal(missingArtifactUrlWorkflow.fetched, false);
assert.deepEqual(missingArtifactUrlWorkflow.missing.slice(0, 2), ["artifact-url", "fetch"]);
assert.equal(failedArtifactUrlWorkflow.ready, false);
assert.equal(failedArtifactUrlWorkflow.fetched, false);
assert.equal(failedArtifactUrlWorkflow.httpStatus, 404);
assert.match(failedArtifactUrlWorkflow.fetchError, /404/);
assert.equal(failedArtifactUrlWorkflow.missing[0], "artifact-fetch");
assert.equal(
failedArtifactUrlWorkflow.actionPlan.nextCommand,
"wasm-port/tests/host/verify_project_release_gate.sh",
);
assert.equal(missingArtifactUrlWorkflowSummary.phase, "blocked");
assert.match(missingArtifactUrlWorkflowSummary.statusLine, /^Blocked: artifact-url, fetch/);
assert.equal(
missingArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "artifact-url")?.value,
"missing",
);
assert.equal(failedArtifactUrlWorkflowSummary.phase, "blocked");
assert.match(failedArtifactUrlWorkflowSummary.statusLine, /^Blocked: artifact-fetch/);
assert.equal(
failedArtifactUrlWorkflowSummary.rows.find(({ id }) => id === "fetch")?.value,
"blocked",
);
console.log("project_release_gate_manifest_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/sdk/node/verify_project_release_gate_manifest.mjs"

View File

@@ -0,0 +1,926 @@
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import {
INI_PANEL_ENTRIES,
analyzeIniRuntimeBoundaries,
createControlPageBrowserApiManifest,
createControlPageBrowserApiMethods,
createControlPageBrowserApiSchema,
createIniPanelLaunchApiManifest,
createIniPanelLauncherLinkViewModel,
createIniPanelShellApiSurfaceInventory,
createIniPanelShellControlPageContract,
createIniPanelShellIntegrationManifest,
createIniPanelShellIntegrationReadiness,
createIniPanelShellIntegrationSchema,
createIniPanelShellIntegrationWorkflowPlan,
createIniPanelShellSessionReadinessWorkflowReport,
createIniPanelShellViewModel,
createIniPanelShellWorkflowOverviewContract,
createIniPanelShellWorkflowOverviewEmbeddingMountDomContract,
createIniPanelShellWorkflowOverviewEmbeddingMountDomReadiness,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactDisplayViewModel,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactDomContract,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactDomReadiness,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryViewModel,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactRenderState,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlGateExecutionSummaryViewModel,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowActionPlan,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowSummaryViewModel,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationActionPlan,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationSummaryViewModel,
createLinuxCncIniSdk,
createLinuxCncInterpSdk,
createMachineSessionSnapshotPayload,
createMachineSessionPersistenceDisplayViewModel,
createMachineSessionPersistenceRenderState,
createMachineSessionPersistenceSummary,
createProjectBatchAcceptanceActionPlan,
createProjectBatchAcceptanceCapabilityMatrix,
createProjectBatchAcceptanceChecklist,
createProjectBatchAcceptanceReport,
createProjectBatchAcceptanceReportJsonWorkflow,
createProjectBatchAcceptanceReportUrlWorkflowActionPlan,
createProjectBatchAcceptanceReportUrlWorkflowSummaryViewModel,
createProjectBatchAcceptanceReportValidationActionPlan,
createProjectBatchAcceptanceReportValidationSummaryViewModel,
createProjectBatchAcceptanceReportValidation,
createProjectBatchAcceptanceSummaryViewModel,
createProjectBatchAcceptanceWorkflow,
createProjectReleaseGateActionPlan,
createProjectReleaseGateExecutionManifest,
createProjectReleaseGateExecutionSummaryViewModel,
createProjectReleaseGateManifest,
createProjectReleaseGateResultMatrix,
createProjectReleaseReadinessArtifactJsonWorkflow,
createProjectReleaseReadinessArtifactUrlWorkflowActionPlan,
createProjectReleaseReadinessArtifactUrlWorkflowSummaryViewModel,
createProjectReleaseReadinessArtifactValidation,
createProjectReleaseReadinessArtifactValidationActionPlan,
createProjectReleaseReadinessArtifactValidationSummaryViewModel,
createProjectReleaseReadinessReport,
createProjectReleaseReadinessSummaryViewModel,
parseProjectReleaseReadinessArtifactJson,
createSessionSnapshot,
defaultMachinePaths,
gcodeFilenameFromProgramPath,
gcodeProgramPath,
getIniPanelEntryByHref,
getIniPanelEntryManifest,
getOpfsRoot,
getVisibleIniPanelEntries,
isSupportedIniPanelControlPageApiManifest,
isSupportedIniPanelLaunchApiManifest,
isSupportedIniPanelShellIntegrationManifest,
loadIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrl,
loadProjectBatchAcceptanceReportUrlWorkflow,
runIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflow,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflowSummaryViewModel,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflowActionPlan,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflowRenderState,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflowDomContract,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflowDomReadiness,
renderIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflowState,
mountIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflowState,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowRenderState,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowDomContract,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowDomReadiness,
renderIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowState,
mountIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowState,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryRenderState,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryDomContract,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryDomReadiness,
renderIniPanelShellWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryState,
mountIniPanelShellWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryState,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationActionPlanRenderState,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationActionPlanDomContract,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationActionPlanDomReadiness,
renderIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationActionPlanState,
mountIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationActionPlanState,
loadProjectReleaseReadinessArtifactUrlWorkflow,
loadMachineSessionFromOpfs,
loadMachineSessionSnapshot,
loadMachineTextFiles,
loadMachineToolTableFromOpfs,
loadTextFile,
machineFilePaths,
machineIniPath,
mountIniPanelShellWorkflowOverviewEmbeddingMountState,
mountIniPanelShellWorkflowOverviewReleaseReadinessArtifactState,
normalizeOpfsPath,
parameterFilePath,
planIniFileContextStaging,
planSimConfigStaging,
readMachineSessionReadiness,
renderIniPanelShellWorkflowOverviewEmbeddingMountState,
renderIniPanelShellWorkflowOverviewReleaseReadinessArtifactState,
restoreMachineParametersFromOpfs,
saveMachineSessionSnapshot,
saveMachineTextFiles,
saveSessionSnapshot,
saveTextFile,
sessionSnapshotPath,
toolTablePath,
validateIniPanelShellWorkflowOverviewReleaseReadinessArtifactJson,
validateSessionSnapshot,
} from "../../../runtime/sdk/src/index.js";
const __dirname = dirname(fileURLToPath(import.meta.url));
const root = resolve(__dirname, "../../..");
const sdkIndexText = readFileSync(resolve(root, "runtime/sdk/src/index.js"), "utf8");
const sdkReadmeText = readFileSync(resolve(root, "runtime/sdk/README.md"), "utf8");
const trackerText = readFileSync(resolve(root, "../PROJECT_COMPLETION_TRACKER.md"), "utf8");
const requiredExports = [
["createLinuxCncIniSdk", createLinuxCncIniSdk],
["createLinuxCncInterpSdk", createLinuxCncInterpSdk],
["createProjectBatchAcceptanceActionPlan", createProjectBatchAcceptanceActionPlan],
["createProjectBatchAcceptanceCapabilityMatrix", createProjectBatchAcceptanceCapabilityMatrix],
["createProjectBatchAcceptanceChecklist", createProjectBatchAcceptanceChecklist],
["createProjectBatchAcceptanceReport", createProjectBatchAcceptanceReport],
["createProjectBatchAcceptanceReportJsonWorkflow", createProjectBatchAcceptanceReportJsonWorkflow],
[
"createProjectBatchAcceptanceReportUrlWorkflowActionPlan",
createProjectBatchAcceptanceReportUrlWorkflowActionPlan,
],
[
"createProjectBatchAcceptanceReportUrlWorkflowSummaryViewModel",
createProjectBatchAcceptanceReportUrlWorkflowSummaryViewModel,
],
[
"createProjectBatchAcceptanceReportValidationActionPlan",
createProjectBatchAcceptanceReportValidationActionPlan,
],
[
"createProjectBatchAcceptanceReportValidationSummaryViewModel",
createProjectBatchAcceptanceReportValidationSummaryViewModel,
],
["createProjectBatchAcceptanceReportValidation", createProjectBatchAcceptanceReportValidation],
["createProjectBatchAcceptanceSummaryViewModel", createProjectBatchAcceptanceSummaryViewModel],
["createProjectBatchAcceptanceWorkflow", createProjectBatchAcceptanceWorkflow],
["createProjectReleaseGateActionPlan", createProjectReleaseGateActionPlan],
["createProjectReleaseGateExecutionManifest", createProjectReleaseGateExecutionManifest],
["createProjectReleaseGateExecutionSummaryViewModel", createProjectReleaseGateExecutionSummaryViewModel],
["createProjectReleaseGateManifest", createProjectReleaseGateManifest],
["createProjectReleaseGateResultMatrix", createProjectReleaseGateResultMatrix],
["createProjectReleaseReadinessArtifactJsonWorkflow", createProjectReleaseReadinessArtifactJsonWorkflow],
[
"createProjectReleaseReadinessArtifactUrlWorkflowActionPlan",
createProjectReleaseReadinessArtifactUrlWorkflowActionPlan,
],
[
"createProjectReleaseReadinessArtifactUrlWorkflowSummaryViewModel",
createProjectReleaseReadinessArtifactUrlWorkflowSummaryViewModel,
],
["createProjectReleaseReadinessArtifactValidation", createProjectReleaseReadinessArtifactValidation],
[
"createProjectReleaseReadinessArtifactValidationActionPlan",
createProjectReleaseReadinessArtifactValidationActionPlan,
],
[
"createProjectReleaseReadinessArtifactValidationSummaryViewModel",
createProjectReleaseReadinessArtifactValidationSummaryViewModel,
],
["createProjectReleaseReadinessReport", createProjectReleaseReadinessReport],
["createProjectReleaseReadinessSummaryViewModel", createProjectReleaseReadinessSummaryViewModel],
["parseProjectReleaseReadinessArtifactJson", parseProjectReleaseReadinessArtifactJson],
[
"validateIniPanelShellWorkflowOverviewReleaseReadinessArtifactJson",
validateIniPanelShellWorkflowOverviewReleaseReadinessArtifactJson,
],
[
"runIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflow",
runIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflow,
],
[
"createIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflowSummaryViewModel",
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflowSummaryViewModel,
],
[
"createIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflowActionPlan",
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflowActionPlan,
],
[
"createIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflowRenderState",
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflowRenderState,
],
[
"createIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflowDomContract",
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflowDomContract,
],
[
"createIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflowDomReadiness",
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflowDomReadiness,
],
[
"renderIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflowState",
renderIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflowState,
],
[
"mountIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflowState",
mountIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflowState,
],
[
"createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowRenderState",
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowRenderState,
],
[
"createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowDomContract",
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowDomContract,
],
[
"createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowDomReadiness",
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowDomReadiness,
],
[
"renderIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowState",
renderIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowState,
],
[
"mountIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowState",
mountIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowState,
],
[
"createIniPanelShellWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryRenderState",
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryRenderState,
],
[
"createIniPanelShellWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryDomContract",
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryDomContract,
],
[
"createIniPanelShellWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryDomReadiness",
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryDomReadiness,
],
[
"renderIniPanelShellWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryState",
renderIniPanelShellWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryState,
],
[
"mountIniPanelShellWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryState",
mountIniPanelShellWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryState,
],
[
"createIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationActionPlanRenderState",
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationActionPlanRenderState,
],
[
"createIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationActionPlanDomContract",
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationActionPlanDomContract,
],
[
"createIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationActionPlanDomReadiness",
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationActionPlanDomReadiness,
],
[
"renderIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationActionPlanState",
renderIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationActionPlanState,
],
[
"mountIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationActionPlanState",
mountIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationActionPlanState,
],
[
"loadIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrl",
loadIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrl,
],
["loadProjectBatchAcceptanceReportUrlWorkflow", loadProjectBatchAcceptanceReportUrlWorkflow],
["loadProjectReleaseReadinessArtifactUrlWorkflow", loadProjectReleaseReadinessArtifactUrlWorkflow],
["planIniFileContextStaging", planIniFileContextStaging],
["planSimConfigStaging", planSimConfigStaging],
["analyzeIniRuntimeBoundaries", analyzeIniRuntimeBoundaries],
["getIniPanelEntryManifest", getIniPanelEntryManifest],
["getIniPanelEntryByHref", getIniPanelEntryByHref],
["getVisibleIniPanelEntries", getVisibleIniPanelEntries],
["createIniPanelLauncherLinkViewModel", createIniPanelLauncherLinkViewModel],
["createMachineSessionPersistenceDisplayViewModel", createMachineSessionPersistenceDisplayViewModel],
["createMachineSessionPersistenceRenderState", createMachineSessionPersistenceRenderState],
["createMachineSessionPersistenceSummary", createMachineSessionPersistenceSummary],
["getOpfsRoot", getOpfsRoot],
["saveTextFile", saveTextFile],
["loadTextFile", loadTextFile],
["machineIniPath", machineIniPath],
["toolTablePath", toolTablePath],
["parameterFilePath", parameterFilePath],
["gcodeProgramPath", gcodeProgramPath],
["sessionSnapshotPath", sessionSnapshotPath],
["defaultMachinePaths", defaultMachinePaths],
["normalizeOpfsPath", normalizeOpfsPath],
["createMachineSessionSnapshotPayload", createMachineSessionSnapshotPayload],
["createSessionSnapshot", createSessionSnapshot],
["saveSessionSnapshot", saveSessionSnapshot],
["saveMachineSessionSnapshot", saveMachineSessionSnapshot],
["loadMachineSessionSnapshot", loadMachineSessionSnapshot],
["validateSessionSnapshot", validateSessionSnapshot],
["machineFilePaths", machineFilePaths],
["saveMachineTextFiles", saveMachineTextFiles],
["loadMachineTextFiles", loadMachineTextFiles],
["gcodeFilenameFromProgramPath", gcodeFilenameFromProgramPath],
["restoreMachineParametersFromOpfs", restoreMachineParametersFromOpfs],
["loadMachineToolTableFromOpfs", loadMachineToolTableFromOpfs],
["loadMachineSessionFromOpfs", loadMachineSessionFromOpfs],
["readMachineSessionReadiness", readMachineSessionReadiness],
["createControlPageBrowserApiSchema", createControlPageBrowserApiSchema],
["createControlPageBrowserApiManifest", createControlPageBrowserApiManifest],
["createControlPageBrowserApiMethods", createControlPageBrowserApiMethods],
["createIniPanelShellControlPageContract", createIniPanelShellControlPageContract],
["isSupportedIniPanelControlPageApiManifest", isSupportedIniPanelControlPageApiManifest],
["createIniPanelLaunchApiManifest", createIniPanelLaunchApiManifest],
["isSupportedIniPanelLaunchApiManifest", isSupportedIniPanelLaunchApiManifest],
["createIniPanelShellIntegrationSchema", createIniPanelShellIntegrationSchema],
["createIniPanelShellIntegrationManifest", createIniPanelShellIntegrationManifest],
["createIniPanelShellIntegrationReadiness", createIniPanelShellIntegrationReadiness],
["createIniPanelShellIntegrationWorkflowPlan", createIniPanelShellIntegrationWorkflowPlan],
["isSupportedIniPanelShellIntegrationManifest", isSupportedIniPanelShellIntegrationManifest],
["createIniPanelShellViewModel", createIniPanelShellViewModel],
["createIniPanelShellApiSurfaceInventory", createIniPanelShellApiSurfaceInventory],
["createIniPanelShellSessionReadinessWorkflowReport", createIniPanelShellSessionReadinessWorkflowReport],
["createIniPanelShellWorkflowOverviewContract", createIniPanelShellWorkflowOverviewContract],
[
"createIniPanelShellWorkflowOverviewEmbeddingMountDomContract",
createIniPanelShellWorkflowOverviewEmbeddingMountDomContract,
],
[
"createIniPanelShellWorkflowOverviewEmbeddingMountDomReadiness",
createIniPanelShellWorkflowOverviewEmbeddingMountDomReadiness,
],
[
"createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowSummaryViewModel",
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowSummaryViewModel,
],
[
"createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowActionPlan",
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowActionPlan,
],
[
"createIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationSummaryViewModel",
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationSummaryViewModel,
],
[
"createIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationActionPlan",
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationActionPlan,
],
[
"createIniPanelShellWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryViewModel",
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryViewModel,
],
[
"createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlGateExecutionSummaryViewModel",
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlGateExecutionSummaryViewModel,
],
[
"createIniPanelShellWorkflowOverviewReleaseReadinessArtifactDisplayViewModel",
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactDisplayViewModel,
],
[
"createIniPanelShellWorkflowOverviewReleaseReadinessArtifactRenderState",
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactRenderState,
],
[
"createIniPanelShellWorkflowOverviewReleaseReadinessArtifactDomContract",
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactDomContract,
],
[
"createIniPanelShellWorkflowOverviewReleaseReadinessArtifactDomReadiness",
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactDomReadiness,
],
[
"renderIniPanelShellWorkflowOverviewEmbeddingMountState",
renderIniPanelShellWorkflowOverviewEmbeddingMountState,
],
[
"renderIniPanelShellWorkflowOverviewReleaseReadinessArtifactState",
renderIniPanelShellWorkflowOverviewReleaseReadinessArtifactState,
],
[
"mountIniPanelShellWorkflowOverviewEmbeddingMountState",
mountIniPanelShellWorkflowOverviewEmbeddingMountState,
],
[
"mountIniPanelShellWorkflowOverviewReleaseReadinessArtifactState",
mountIniPanelShellWorkflowOverviewReleaseReadinessArtifactState,
],
];
for (const [name, value] of requiredExports) {
assert.equal(typeof value, "function", `${name} SDK export drift`);
assert.match(sdkIndexText, new RegExp(`\\b${name}\\b`));
assert.match(sdkReadmeText, new RegExp(`\\b${name}\\b`));
}
assert.equal(machineIniPath("sdk-mill"), "linuxcnc/machines/sdk-mill/machine.ini");
assert.deepEqual(defaultMachinePaths("sdk-mill"), machineFilePaths("sdk-mill"));
assert.equal(gcodeProgramPath("demo.ngc"), "linuxcnc/gcode/demo.ngc");
assert.equal(sessionSnapshotPath("sdk-session"), "linuxcnc/sessions/sdk-session/snapshot.json");
assert.equal(normalizeOpfsPath("linuxcnc/machines/sdk-mill/tool.tbl"), "linuxcnc/machines/sdk-mill/tool.tbl");
assert.equal(gcodeFilenameFromProgramPath("linuxcnc/gcode/demo.ngc"), "demo.ngc");
const releaseReadinessWaiting = createProjectReleaseReadinessReport();
const batchCapabilityMatrix = createProjectBatchAcceptanceCapabilityMatrix({
capabilities: [
{
id: "sdk-surface-api",
type: "api",
label: "SDK surface API",
},
],
});
assert.equal(batchCapabilityMatrix.ready, true);
assert.equal(batchCapabilityMatrix.countsByType.api, 1);
const batchAcceptanceWorkflow = createProjectBatchAcceptanceWorkflow({
batchId: "sdk-surface-batch",
capabilities: [
{
id: "sdk-surface-api",
type: "api",
label: "SDK surface API",
},
],
observedOutputs: ["project_batch_acceptance_workflow_node_smoke=ok"],
});
assert.equal(batchAcceptanceWorkflow.ready, true);
assert.equal(batchAcceptanceWorkflow.capabilityTypes[0], "api");
assert.equal(
createProjectBatchAcceptanceSummaryViewModel(batchAcceptanceWorkflow).statusLine,
"Ready: Batch acceptance evidence complete",
);
assert.equal(
createProjectBatchAcceptanceActionPlan(batchAcceptanceWorkflow).shellScript,
"# Project batch acceptance workflow is already ready.\n",
);
assert.equal(
createProjectBatchAcceptanceChecklist({ workflow: batchAcceptanceWorkflow }).rows[0].value,
"pass: api",
);
assert.equal(
createProjectBatchAcceptanceReport({
batchId: "sdk-surface-batch",
capabilities: batchAcceptanceWorkflow.capabilities,
observedOutputs: ["project_batch_acceptance_workflow_node_smoke=ok"],
}).rows.find(({ id }) => id === "checklist")?.value,
"3/3 passed",
);
const batchAcceptanceReport = createProjectBatchAcceptanceReport({
batchId: "sdk-surface-batch",
capabilities: batchAcceptanceWorkflow.capabilities,
observedOutputs: ["project_batch_acceptance_workflow_node_smoke=ok"],
});
assert.equal(createProjectBatchAcceptanceReportValidation(batchAcceptanceReport).ready, true);
assert.equal(
createProjectBatchAcceptanceReportValidationSummaryViewModel(
createProjectBatchAcceptanceReportValidation(batchAcceptanceReport),
).statusLine,
"Ready: Batch acceptance report evidence complete",
);
assert.equal(
createProjectBatchAcceptanceReportValidationActionPlan(
createProjectBatchAcceptanceReportValidation(batchAcceptanceReport),
).shellScript,
"# Project batch acceptance report validation is already ready.\n",
);
assert.equal(
createProjectBatchAcceptanceReportJsonWorkflow({
reportJson: JSON.stringify(batchAcceptanceReport),
}).ready,
true,
);
const batchAcceptanceReportUrlWorkflow = await loadProjectBatchAcceptanceReportUrlWorkflow({
reportUrl: "/project-batch-acceptance.json",
fetchRef: async () => ({
ok: true,
status: 200,
text: async () => JSON.stringify(batchAcceptanceReport),
}),
});
assert.equal(batchAcceptanceReportUrlWorkflow.ready, true);
assert.equal(
createProjectBatchAcceptanceReportUrlWorkflowSummaryViewModel(
batchAcceptanceReportUrlWorkflow,
).statusLine,
"Ready: Batch report URL workflow complete",
);
assert.equal(
createProjectBatchAcceptanceReportUrlWorkflowActionPlan(
batchAcceptanceReportUrlWorkflow,
).shellScript,
"# Project batch acceptance report URL workflow is already ready.\n",
);
assert.equal(
(await loadProjectBatchAcceptanceReportUrlWorkflow({
reportUrl: "/missing-project-batch-acceptance.json",
fetchRef: async () => ({ ok: false, status: 404, text: async () => "" }),
})).missing[0],
"report-fetch",
);
assert.equal(
createProjectBatchAcceptanceActionPlan(createProjectBatchAcceptanceWorkflow()).nextCommand,
"wasm-port/tests/sdk/node/verify_project_batch_acceptance_workflow.sh",
);
assert.equal(releaseReadinessWaiting.apiName, "project-release-readiness-report");
assert.equal(releaseReadinessWaiting.ready, false);
assert.deepEqual(releaseReadinessWaiting.missing, ["project-release-gate"]);
assert.equal(releaseReadinessWaiting.simConfigInventory.unexpectedFail, 0);
assert.deepEqual(releaseReadinessWaiting.promotedBlockedFamilies, []);
assert.equal(releaseReadinessWaiting.gateActionPlan.apiName, "project-release-gate-action-plan");
assert.equal(releaseReadinessWaiting.gateActionPlan.pendingCount, createProjectReleaseGateManifest().gateCount);
assert.equal(releaseReadinessWaiting.gateActionPlan.nextGateId, "diff-check");
assert.equal(
releaseReadinessWaiting.rows.find(({ id }) => id === "blocked-runtime-families")?.value,
"L4-USER-M-PROCESS, L4-TOOL-DB, L4-PYTHON-REMAP",
);
const releaseReadinessReady = createProjectReleaseReadinessReport({
observedOutputs: ["project_release_gate=ok"],
});
assert.equal(releaseReadinessReady.ready, true);
assert.equal(releaseReadinessReady.releaseGate.passed, true);
assert.deepEqual(releaseReadinessReady.gateManifest, createProjectReleaseGateManifest());
assert.equal(releaseReadinessReady.gateResultMatrix.rows.length, createProjectReleaseGateManifest().gateCount);
assert.equal(createProjectReleaseGateActionPlan({
gateResultMatrix: releaseReadinessReady.gateResultMatrix,
}).ready, false);
assert.equal(createProjectReleaseGateActionPlan({
gateResults: Object.fromEntries(createProjectReleaseGateManifest().gateIds.map((id) => [id, true])),
}).shellScript, "# Project release gates are already marked passed.\n");
assert.equal(createProjectReleaseReadinessSummaryViewModel(releaseReadinessReady).statusLine, "Ready: No blocking reasons");
assert.equal(
createProjectReleaseGateResultMatrix({
observedOutputs: ["project_release_gate=ok"],
}).rows.find(({ id }) => id === "project-release-gate")?.passed,
true,
);
const releaseReadinessArtifact = parseProjectReleaseReadinessArtifactJson(JSON.stringify(createProjectReleaseReadinessReport({
gateResults: Object.fromEntries(createProjectReleaseGateManifest().gateIds.map((id) => [id, true])),
})));
assert.equal(releaseReadinessArtifact.apiName, "project-release-readiness-report");
const releaseReadinessArtifactValidation = createProjectReleaseReadinessArtifactValidation(releaseReadinessArtifact);
assert.deepEqual(releaseReadinessArtifactValidation, {
apiName: "project-release-readiness-artifact-validation",
validationVersion: 1,
phase: "ready",
ready: true,
missing: [],
artifactApiName: "project-release-readiness-report",
artifactVersion: 1,
gateCount: createProjectReleaseGateManifest().gateCount,
expectedGateCount: createProjectReleaseGateManifest().gateCount,
expectedGateIds: createProjectReleaseGateManifest().gateIds,
gateManifestReady: true,
gateExecutionManifestReady: true,
gateExecutionSummaryReady: true,
gateResultMatrixReady: true,
gateActionPlanReady: true,
blockedRuntimeFamilies: [
"L4-USER-M-PROCESS",
"L4-TOOL-DB",
"L4-PYTHON-REMAP",
],
rows: [
{
id: "artifact",
label: "Readiness artifact",
value: "project-release-readiness-report",
},
{
id: "project-release-gate",
label: "Project release gate",
value: "passed",
},
{
id: "gate-manifest",
label: "Gate manifest",
value: "ready",
},
{
id: "gate-execution-manifest",
label: "Gate execution manifest",
value: "ready",
},
{
id: "gate-execution-summary",
label: "Gate execution summary",
value: "ready",
},
{
id: "gate-result-matrix",
label: "Gate result matrix",
value: "ready",
},
{
id: "gate-action-plan",
label: "Gate action plan",
value: "ready",
},
{
id: "validation",
label: "Artifact validation",
value: "ready",
},
],
});
assert.equal(
createProjectReleaseReadinessArtifactValidationSummaryViewModel(
releaseReadinessArtifactValidation,
).statusLine,
"Ready: Artifact evidence complete",
);
assert.equal(
createProjectReleaseReadinessArtifactValidationActionPlan(releaseReadinessArtifactValidation).shellScript,
"# Project release readiness artifact validation is already ready.\n",
);
assert.equal(
createProjectReleaseReadinessArtifactValidationActionPlan(createProjectReleaseReadinessArtifactValidation({}))
.nextCommand,
"wasm-port/tests/host/verify_project_release_gate.sh",
);
assert.equal(
createProjectReleaseReadinessArtifactJsonWorkflow({
artifactJson: JSON.stringify(releaseReadinessArtifact),
}).summaryViewModel.statusLine,
"Ready: Artifact evidence complete",
);
assert.equal(
createProjectReleaseReadinessArtifactJsonWorkflow({ artifactJson: "not-json" }).parsed,
false,
);
assert.equal(
(await loadProjectReleaseReadinessArtifactUrlWorkflow({
artifactUrl: "/project-release-readiness.json",
fetchRef: async () => ({
ok: true,
status: 200,
text: async () => JSON.stringify(releaseReadinessArtifact),
}),
})).summaryViewModel.statusLine,
"Ready: Artifact evidence complete",
);
assert.equal(
createProjectReleaseReadinessArtifactUrlWorkflowSummaryViewModel(
await loadProjectReleaseReadinessArtifactUrlWorkflow({
artifactUrl: "/project-release-readiness.json",
fetchRef: async () => ({
ok: true,
status: 200,
text: async () => JSON.stringify(releaseReadinessArtifact),
}),
}),
).statusLine,
"Ready: Artifact URL workflow complete",
);
assert.equal(
createProjectReleaseReadinessArtifactUrlWorkflowActionPlan(
await loadProjectReleaseReadinessArtifactUrlWorkflow({
artifactUrl: "/project-release-readiness.json",
fetchRef: async () => ({
ok: true,
status: 200,
text: async () => JSON.stringify(releaseReadinessArtifact),
}),
}),
).shellScript,
"# Project release readiness artifact URL workflow is already ready.\n",
);
assert.equal(
(await loadProjectReleaseReadinessArtifactUrlWorkflow({
artifactUrl: "/missing.json",
fetchRef: async () => ({ ok: false, status: 404, text: async () => "" }),
})).missing[0],
"artifact-fetch",
);
assert.deepEqual(
createProjectReleaseReadinessArtifactValidation({}).missing,
[
"apiName",
"reportVersion",
"phase",
"ready",
"missing",
"releaseGate.command",
"releaseGate.passed",
"releaseGate.expectedOutput",
"simConfigInventory.executed",
"simConfigInventory.passed",
"simConfigInventory.skipped",
"simConfigInventory.unexpectedFail",
"blockedRuntimeFamilies",
"gateManifest",
"gateExecutionManifest",
"gateExecutionSummaryViewModel",
"gateResultMatrix",
"gateActionPlan",
"gates.length",
"gates.passed",
"rows.project-release-gate",
],
);
assert.equal(
validateIniPanelShellWorkflowOverviewReleaseReadinessArtifactJson(JSON.stringify(releaseReadinessArtifact)).ready,
true,
);
assert.equal(
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationActionPlan(
validateIniPanelShellWorkflowOverviewReleaseReadinessArtifactJson(JSON.stringify(releaseReadinessArtifact)),
).shellScript,
"# Project release readiness artifact validation is already ready.\n",
);
assert.equal(
runIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflow({
artifactJson: JSON.stringify(releaseReadinessArtifact),
}).statusLine,
"Ready: Artifact JSON workflow complete",
);
assert.equal(
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflowSummaryViewModel(
runIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflow({
artifactJson: JSON.stringify(releaseReadinessArtifact),
}),
).statusLine,
"Ready: Artifact JSON workflow complete",
);
assert.equal(
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflowActionPlan(
runIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflow({
artifactJson: JSON.stringify(releaseReadinessArtifact),
}),
).nextCommand,
null,
);
assert.equal(
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflowRenderState(
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflowSummaryViewModel(
runIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflow({
artifactJson: JSON.stringify(releaseReadinessArtifact),
}),
),
).dataset.handoffScope,
"workflow-overview-release-readiness-artifact-json-workflow",
);
const releaseReadinessArtifactUrlWorkflow = await loadIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrl({
artifactUrl: "/project-release-readiness.json",
fetchRef: async () => ({
ok: true,
status: 200,
text: async () => JSON.stringify(releaseReadinessArtifact),
}),
});
assert.equal(releaseReadinessArtifactUrlWorkflow.ready, true);
assert.equal(
releaseReadinessArtifactUrlWorkflow.validationSummaryViewModel.statusLine,
"Ready: Artifact evidence complete",
);
assert.equal(
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowSummaryViewModel(
releaseReadinessArtifactUrlWorkflow,
).statusLine,
"Ready: Artifact URL workflow complete",
);
assert.equal(
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowActionPlan(
releaseReadinessArtifactUrlWorkflow,
).shellScript,
"# Project release readiness artifact URL workflow is already ready.\n",
);
assert.equal(
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactRenderState(
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactDisplayViewModel(
validateIniPanelShellWorkflowOverviewReleaseReadinessArtifactJson(JSON.stringify(releaseReadinessArtifact)),
),
).dataset.handoffScope,
"workflow-overview-release-readiness-artifact",
);
assert.equal(
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactDomContract().selectors.rows,
"[data-workflow-overview-release-readiness-artifact-rows]",
);
assert.equal(createIniPanelShellWorkflowOverviewReleaseReadinessArtifactDomReadiness().ready, false);
assert.equal(
createProjectReleaseReadinessReport({
observedOutputs: ["project_release_gate=ok"],
promotedRuntimeFamilies: ["L4-TOOL-DB"],
}).missing.includes("blocked-runtime-families"),
true,
);
assert.deepEqual(getIniPanelEntryManifest(), INI_PANEL_ENTRIES);
assert.deepEqual(getVisibleIniPanelEntries(), INI_PANEL_ENTRIES);
assert.deepEqual(getIniPanelEntryByHref("./control-page.html"), {
id: "control-view",
title: "Open read-only control view",
href: "./control-page.html",
});
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",
},
{
entryId: "workflow-overview",
href: "./workflow-overview.html",
label: "Open shell handoff overview",
},
]);
assert.equal(
createMachineSessionPersistenceSummary({
state: {
badges: { opfs: "OPFS: ready" },
fields: { sessionSnapshot: "sdk-session/snapshot.json", sessionIni: "/work/session-machine.ini" },
sessionLoad: { iniWasmPath: "/work/session-machine.ini" },
sessionReadiness: { phase: "ready", ready: true, missing: [] },
},
}).rows.find(({ id }) => id === "session-load")?.value,
"loaded: /work/session-machine.ini",
);
assert.equal(
createMachineSessionPersistenceRenderState(
createMachineSessionPersistenceDisplayViewModel(createMachineSessionPersistenceSummary()),
).apiName,
"machine-session-persistence-render-state",
);
const controlPageApiSchema = createControlPageBrowserApiSchema();
const controlPageApiManifest = createControlPageBrowserApiManifest();
assert.equal(controlPageApiSchema.apiName, "ini-panel-control-page");
assert.equal(isSupportedIniPanelControlPageApiManifest(controlPageApiManifest), true);
assert.deepEqual(controlPageApiManifest.methods, createControlPageBrowserApiMethods());
assert.equal(
createIniPanelShellControlPageContract().readonlyStatusApiManifest.apiName,
"machine-session-readonly-status",
);
const launchManifest = createIniPanelLaunchApiManifest();
assert.equal(isSupportedIniPanelLaunchApiManifest(launchManifest), true);
assert.equal(
launchManifest.methods.includes("mountWorkflowOverviewEmbeddingMountState"),
true,
);
assert.deepEqual(
launchManifest.visibleEntries,
getVisibleIniPanelEntries().map(({ id, href, title }) => ({ id, href, title })),
);
const shellIntegrationManifest = createIniPanelShellIntegrationManifest();
assert.equal(createIniPanelShellIntegrationSchema().apiName, "ini-panel-shell-integration");
assert.equal(isSupportedIniPanelShellIntegrationManifest(shellIntegrationManifest), true);
assert.equal(createIniPanelShellIntegrationReadiness(shellIntegrationManifest).ready, true);
assert.equal(createIniPanelShellIntegrationWorkflowPlan({
manifest: shellIntegrationManifest,
}).ready, true);
assert.equal(
createIniPanelShellWorkflowOverviewContract().methodNames.includes("getWorkflowOverviewApiSurfaceInventory"),
true,
);
const shellViewModel = createIniPanelShellViewModel();
assert.equal(
shellViewModel.launchApiManifest.methods.includes("mountWorkflowOverviewEmbeddingMountState"),
true,
);
assert.equal(
shellViewModel.launchApiManifest.methods.includes("getShellSessionReadinessWorkflowReport"),
true,
);
assert.equal(
createIniPanelShellSessionReadinessWorkflowReport().apiName,
"ini-panel-shell-session-readiness-workflow-report",
);
assert.equal(
createIniPanelShellApiSurfaceInventory().apiName,
"ini-panel-shell-api-surface-inventory",
);
assert.equal(shellViewModel.shellApiSurfaceInventory.ready, true);
assert.equal(
shellViewModel.shellSessionHandoffWorkflowSummary.rows.find(
({ id }) => id === "persistence-summary",
)?.value,
"waiting: 5 missing",
);
assert.equal(
shellViewModel.shellSessionHandoffWorkflowSummary.rows.find(
({ id }) => id === "api-surface-inventory",
)?.value,
"ready: 6 rows",
);
assert.equal(
shellViewModel.mountWorkflowOverviewEmbeddingMountState,
mountIniPanelShellWorkflowOverviewEmbeddingMountState,
);
const blockedMount = mountIniPanelShellWorkflowOverviewEmbeddingMountState({ documentRef: null });
assert.equal(blockedMount.apiName, "ini-panel-shell-workflow-overview-embedding-mount-dom-mount");
assert.equal(blockedMount.ready, false);
assert.deepEqual(blockedMount.domReadiness.missing, ["document", "mount", "status", "rows"]);
assert.match(sdkReadmeText, /OPFS\/session persistence/);
assert.match(sdkReadmeText, /INI panel shell handoff/);
assert.match(sdkReadmeText, /workflow overview embedding/);
assert.match(trackerText, /SDK\/API surface/);
console.log("sdk_surface_node_smoke=ok");

View File

@@ -0,0 +1,9 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "$0")/../../.." && pwd)"
node "$ROOT_DIR/tests/sdk/node/verify_sdk_surface.mjs"
"$ROOT_DIR/tests/sdk/node/verify_project_release_gate_manifest.sh"
"$ROOT_DIR/tests/sdk/node/verify_project_release_artifact_url_workflow.sh"
"$ROOT_DIR/tests/sdk/node/verify_project_batch_acceptance_workflow.sh"

View File

@@ -0,0 +1,36 @@
import { mkdirSync, renameSync, writeFileSync } from "node:fs";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import { createProjectBatchAcceptanceReport } from "../../../runtime/sdk/src/index.js";
const __dirname = dirname(fileURLToPath(import.meta.url));
const root = resolve(__dirname, "../../..");
const outputPath = process.argv[2] ?? resolve(root, "build/project-batch-acceptance.json");
const report = createProjectBatchAcceptanceReport({
batchId: "project-batch-acceptance-artifact",
capabilities: [
{
id: "project-batch-acceptance-report-artifact",
type: "workflow",
label: "Project batch acceptance report artifact",
evidence: "project_batch_acceptance_artifact_node_smoke=ok",
command: "wasm-port/tests/sdk/node/verify_project_batch_acceptance_workflow.sh",
},
{
id: "project-batch-acceptance-workflow-gate",
type: "gate",
label: "Project batch acceptance workflow gate",
evidence: "project_batch_acceptance_workflow_node_smoke=ok",
command: "wasm-port/tests/sdk/node/verify_project_batch_acceptance_workflow.sh",
},
],
observedOutputs: ["project_batch_acceptance_workflow_node_smoke=ok"],
});
mkdirSync(dirname(outputPath), { recursive: true });
writeFileSync(`${outputPath}.tmp`, `${JSON.stringify(report, null, 2)}\n`);
renameSync(`${outputPath}.tmp`, outputPath);
console.log(`project_batch_acceptance_artifact=${outputPath}`);

View File

@@ -93,6 +93,7 @@ assert.deepEqual(controller.readStatus(), {
overview: null,
controlView: expectedView,
report: expectedStateBundle.report,
sessionReadiness: null,
runReadiness: null,
run: null,
session: null,
@@ -109,6 +110,7 @@ assert.deepEqual(controller.loadSessionState(), {
controlViewReady: true,
stateBundle: expectedStateBundle,
workflowStatus: expectedWorkflowStatus,
sessionReadiness: null,
controlView: expectedView,
error: null,
});

View File

@@ -21,6 +21,11 @@ const workflowStatus = {
overview: {
phase: "ready",
},
sessionReadiness: {
phase: "ready",
ready: true,
missing: [],
},
lastAction: "load-session",
error: null,
};
@@ -40,6 +45,7 @@ assert.deepEqual(createControlPageSessionLoadState({ phase: "waiting-panel" }),
controlViewReady: false,
stateBundle: null,
workflowStatus: null,
sessionReadiness: null,
controlView: null,
error: null,
});
@@ -81,6 +87,7 @@ assert.deepEqual(
apiReady: true,
stateBundle,
workflowStatus,
sessionReadiness: workflowStatus.sessionReadiness,
}),
);
@@ -101,6 +108,7 @@ assert.deepEqual(
controlViewReady: true,
stateBundle,
workflowStatus,
sessionReadiness: workflowStatus.sessionReadiness,
controlView,
}),
);

View File

@@ -6,20 +6,86 @@ import { dirname, resolve } from "node:path";
import {
INI_PANEL_ENTRIES,
controlPageStatusText,
createControlPageBrowserApiManifest,
createControlPageSessionLoadState,
createControlPageBrowserApiMethods,
createControlPageBrowserApiSchema,
createIniPanelLaunchApiManifest,
createIniPanelLaunchApiSchema,
createIniPanelLauncherLinkViewModel,
createIniPanelShellControlPageContract,
createIniPanelShellIntegrationManifest,
createIniPanelShellIntegrationReadiness,
createIniPanelShellIntegrationSchema,
createIniPanelShellIntegrationWorkflowPlan,
createIniPanelShellSessionHandoffActionPlan,
createIniPanelShellSessionHandoffChecklist,
createIniPanelShellSessionHandoffCompatibilityReport,
createIniPanelShellSessionHandoffDisplayViewModel,
createIniPanelShellSessionHandoffPackage,
createIniPanelShellSessionHandoffPackageDisplayViewModel,
createIniPanelShellSessionHandoffPackageMountDisplayViewModel,
createIniPanelShellSessionHandoffPackageMountRenderState,
createIniPanelShellSessionHandoffPackageMountState,
createIniPanelShellSessionHandoffMountDomContract,
createIniPanelShellSessionHandoffMountDomReadiness,
mountIniPanelShellSessionHandoff,
mountIniPanelShellSessionHandoffFromControlPage,
readIniPanelShellSessionHandoffStatusFromControlPage,
createIniPanelShellSessionHandoffWorkflowSummary,
createIniPanelShellSessionHandoffWorkflowDisplayViewModel,
createIniPanelShellSessionHandoffWorkflowRenderState,
createIniPanelShellSessionHandoffWorkflowDomContract,
createIniPanelShellSessionHandoffWorkflowDomReadiness,
createIniPanelShellWorkflowOverviewContract,
createIniPanelShellWorkflowOverviewReadiness,
createIniPanelShellWorkflowOverviewEmbeddingReport,
createIniPanelShellWorkflowOverviewEmbeddingDisplayViewModel,
createIniPanelShellWorkflowOverviewEmbeddingRenderState,
createIniPanelShellWorkflowOverviewEmbeddingDomContract,
createIniPanelShellWorkflowOverviewEmbeddingDomReadiness,
mountIniPanelShellWorkflowOverviewEmbedding,
renderIniPanelShellWorkflowOverviewEmbeddingState,
mountIniPanelShellWorkflowOverviewEmbeddingState,
createIniPanelShellWorkflowOverviewEmbeddingMountDisplayViewModel,
createIniPanelShellWorkflowOverviewEmbeddingMountRenderState,
createIniPanelShellWorkflowOverviewEmbeddingMountDomContract,
createIniPanelShellWorkflowOverviewEmbeddingMountDomReadiness,
renderIniPanelShellWorkflowOverviewEmbeddingMountState,
mountIniPanelShellWorkflowOverviewEmbeddingMountState,
validateIniPanelShellWorkflowOverviewReleaseReadinessArtifactJson,
loadIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrl,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactDisplayViewModel,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactRenderState,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactDomContract,
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactDomReadiness,
renderIniPanelShellWorkflowOverviewReleaseReadinessArtifactState,
mountIniPanelShellWorkflowOverviewReleaseReadinessArtifactState,
renderIniPanelShellSessionHandoffWorkflowState,
mountIniPanelShellSessionHandoffWorkflow,
renderIniPanelShellSessionHandoffMountState,
createIniPanelShellSessionHandoffPackageReport,
createIniPanelShellSessionHandoffPackageRenderState,
createIniPanelShellSessionHandoffPackageSchema,
createIniPanelShellSessionHandoffRenderState,
createIniPanelShellSessionHandoffSnapshot,
createIniPanelShellSessionHandoffSummary,
createIniPanelShellSessionHandoffSummarySchema,
createIniPanelShellViewModel,
createMachineSessionReadonlyStatus,
createMachineSessionReadonlyStatusApiManifest,
createMachineSessionReadinessFieldState,
machineSessionReadinessLogLines,
readMachineSessionReadinessWorkflow,
loadMachineSessionForControlPageWorkflow,
readMachineSessionReadonlyStatus,
refreshMachineSessionControlPage,
getVisibleIniPanelEntries,
isSupportedIniPanelControlPageApiManifest,
isSupportedIniPanelLaunchApiManifest,
isSupportedIniPanelShellIntegrationManifest,
isSupportedIniPanelShellSessionHandoffPackage,
isSupportedIniPanelShellSessionHandoffSummary,
} from "../../../runtime/ui/ini-panel/ui-shell.js";
const __dirname = dirname(fileURLToPath(import.meta.url));
@@ -28,6 +94,11 @@ const docText = readFileSync(resolve(root, "docs/panel-entry.md"), "utf8");
const shellText = readFileSync(resolve(root, "runtime/ui/ini-panel/ui-shell.js"), "utf8");
const controlPageText = readFileSync(resolve(root, "runtime/ui/ini-panel/control-page.js"), "utf8");
const launchText = readFileSync(resolve(root, "runtime/ui/ini-panel/launch.html"), "utf8");
const browserIniPanelText = readFileSync(resolve(root, "tests/browser/verify_ini_panel_browser.sh"), "utf8");
const shellIntegrationWorkflowSmokeText = readFileSync(
resolve(root, "tests/browser/ini_panel_shell_integration_workflow_smoke.html"),
"utf8",
);
const refreshWorkflowText = readFileSync(
resolve(root, "runtime/ui/ini-panel/control-page-refresh-workflow.js"),
"utf8",
@@ -36,23 +107,123 @@ const sessionWorkflowText = readFileSync(
resolve(root, "runtime/ui/ini-panel/control-page-session-workflow.js"),
"utf8",
);
const iniPanelSessionWorkflowText = readFileSync(
resolve(root, "runtime/ui/ini-panel/session-workflow.js"),
"utf8",
);
const requiredShellExports = [
"INI_PANEL_ENTRIES",
"controlPageStatusText",
"createControlPageBrowserApiManifest",
"createControlPageBrowserApiMethods",
"createControlPageBrowserApiSchema",
"createControlPageSessionLoadState",
"createIniPanelLaunchApiManifest",
"createIniPanelLaunchApiSchema",
"createIniPanelLauncherLinkViewModel",
"createIniPanelShellControlPageContract",
"createIniPanelShellIntegrationManifest",
"createIniPanelShellIntegrationReadiness",
"createIniPanelShellIntegrationSchema",
"createIniPanelShellIntegrationWorkflowPlan",
"createIniPanelShellSessionHandoffActionPlan",
"createIniPanelShellSessionHandoffChecklist",
"createIniPanelShellSessionHandoffCompatibilityReport",
"createIniPanelShellSessionHandoffDisplayViewModel",
"createIniPanelShellSessionHandoffPackage",
"createIniPanelShellSessionHandoffPackageDisplayViewModel",
"createIniPanelShellSessionHandoffPackageMountDisplayViewModel",
"createIniPanelShellSessionHandoffPackageMountRenderState",
"createIniPanelShellSessionHandoffPackageMountState",
"createIniPanelShellSessionHandoffMountDomContract",
"createIniPanelShellSessionHandoffMountDomReadiness",
"mountIniPanelShellSessionHandoff",
"mountIniPanelShellSessionHandoffFromControlPage",
"readIniPanelShellSessionHandoffStatusFromControlPage",
"createIniPanelShellSessionHandoffWorkflowSummary",
"createIniPanelShellSessionHandoffWorkflowDisplayViewModel",
"createIniPanelShellSessionHandoffWorkflowRenderState",
"createIniPanelShellSessionHandoffWorkflowDomContract",
"createIniPanelShellSessionHandoffWorkflowDomReadiness",
"createIniPanelShellWorkflowOverviewContract",
"createIniPanelShellWorkflowOverviewReadiness",
"createIniPanelShellWorkflowOverviewEmbeddingReport",
"createIniPanelShellWorkflowOverviewEmbeddingDisplayViewModel",
"createIniPanelShellWorkflowOverviewEmbeddingRenderState",
"createIniPanelShellWorkflowOverviewEmbeddingDomContract",
"createIniPanelShellWorkflowOverviewEmbeddingDomReadiness",
"mountIniPanelShellWorkflowOverviewEmbedding",
"renderIniPanelShellWorkflowOverviewEmbeddingState",
"mountIniPanelShellWorkflowOverviewEmbeddingState",
"createIniPanelShellWorkflowOverviewEmbeddingMountDisplayViewModel",
"createIniPanelShellWorkflowOverviewEmbeddingMountRenderState",
"createIniPanelShellWorkflowOverviewEmbeddingMountDomContract",
"createIniPanelShellWorkflowOverviewEmbeddingMountDomReadiness",
"renderIniPanelShellWorkflowOverviewEmbeddingMountState",
"mountIniPanelShellWorkflowOverviewEmbeddingMountState",
"validateIniPanelShellWorkflowOverviewReleaseReadinessArtifactJson",
"runIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflow",
"createIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflowSummaryViewModel",
"createIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflowActionPlan",
"createIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflowDomContract",
"createIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflowDomReadiness",
"renderIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflowState",
"mountIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflowState",
"createIniPanelShellWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryViewModel",
"createIniPanelShellWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryRenderState",
"createIniPanelShellWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryDomContract",
"createIniPanelShellWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryDomReadiness",
"renderIniPanelShellWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryState",
"mountIniPanelShellWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryState",
"loadIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrl",
"createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowSummaryViewModel",
"createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowActionPlan",
"createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowRenderState",
"createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowDomContract",
"createIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowDomReadiness",
"renderIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowState",
"mountIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrlWorkflowState",
"createIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationSummaryViewModel",
"createIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationActionPlan",
"createIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationActionPlanRenderState",
"createIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationActionPlanDomContract",
"createIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationActionPlanDomReadiness",
"renderIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationActionPlanState",
"mountIniPanelShellWorkflowOverviewReleaseReadinessArtifactValidationActionPlanState",
"createIniPanelShellWorkflowOverviewReleaseReadinessArtifactDisplayViewModel",
"createIniPanelShellWorkflowOverviewReleaseReadinessArtifactRenderState",
"createIniPanelShellWorkflowOverviewReleaseReadinessArtifactDomContract",
"createIniPanelShellWorkflowOverviewReleaseReadinessArtifactDomReadiness",
"renderIniPanelShellWorkflowOverviewReleaseReadinessArtifactState",
"mountIniPanelShellWorkflowOverviewReleaseReadinessArtifactState",
"renderIniPanelShellSessionHandoffWorkflowState",
"mountIniPanelShellSessionHandoffWorkflow",
"renderIniPanelShellSessionHandoffMountState",
"createIniPanelShellSessionHandoffPackageReport",
"createIniPanelShellSessionHandoffPackageRenderState",
"createIniPanelShellSessionHandoffPackageSchema",
"createIniPanelShellSessionHandoffRenderState",
"createIniPanelShellSessionHandoffSnapshot",
"createIniPanelShellSessionHandoffSummary",
"createIniPanelShellSessionHandoffSummarySchema",
"createIniPanelShellViewModel",
"getIniPanelEntryByHref",
"getVisibleIniPanelEntries",
"createMachineSessionPersistenceDisplayViewModel",
"createMachineSessionPersistenceRenderState",
"createMachineSessionPersistenceSummary",
"isSupportedIniPanelControlPageApiManifest",
"isSupportedIniPanelLaunchApiManifest",
"isSupportedIniPanelShellIntegrationManifest",
"isSupportedIniPanelShellSessionHandoffPackage",
"isSupportedIniPanelShellSessionHandoffSummary",
"createMachineSessionControlPageController",
"createMachineSessionReadonlyStatus",
"createMachineSessionReadonlyStatusApiManifest",
"createMachineSessionReadinessFieldState",
"machineSessionReadinessLogLines",
"readMachineSessionReadinessWorkflow",
"loadMachineSessionForControlPageWorkflow",
"readMachineSessionReadonlyStatus",
"refreshMachineSessionControlPage",
@@ -81,10 +252,20 @@ for (const exportName of [
assert.match(docText, new RegExp(`\\b${exportName}\\b`));
}
for (const exportName of [
"createMachineSessionReadinessFieldState",
"machineSessionReadinessLogLines",
"readMachineSessionReadinessWorkflow",
]) {
assert.match(iniPanelSessionWorkflowText, new RegExp(`\\bexport (?:async )?function ${exportName}\\b`));
assert.match(docText, new RegExp(`\\b${exportName}\\b`));
}
const requiredPages = [
["Launch", "runtime/ui/ini-panel/launch.html"],
["Edit and run", "runtime/ui/ini-panel/index.html"],
["Read-only control view", "runtime/ui/ini-panel/control-page.html"],
["Workflow overview", "runtime/ui/ini-panel/workflow-overview.html"],
];
for (const [label, path] of requiredPages) {
@@ -102,10 +283,183 @@ assert.deepEqual(
);
assert.deepEqual(createIniPanelShellViewModel().pages, getVisibleIniPanelEntries());
assert.deepEqual(createIniPanelShellViewModel().launcherLinks, createIniPanelLauncherLinkViewModel());
assert.deepEqual(
createIniPanelShellViewModel().shellIntegrationSchema,
createIniPanelShellIntegrationSchema(),
);
assert.deepEqual(
createIniPanelShellViewModel().shellIntegrationManifest,
createIniPanelShellIntegrationManifest(),
);
assert.deepEqual(
createIniPanelShellViewModel().shellIntegrationReadiness,
createIniPanelShellIntegrationReadiness(createIniPanelShellViewModel().shellIntegrationManifest),
);
assert.deepEqual(
createIniPanelShellViewModel().shellIntegrationWorkflowPlan,
createIniPanelShellIntegrationWorkflowPlan({
manifest: createIniPanelShellViewModel().shellIntegrationManifest,
readiness: createIniPanelShellViewModel().shellIntegrationReadiness,
}),
);
assert.deepEqual(
createIniPanelShellViewModel().shellSessionHandoffSummary,
createIniPanelShellSessionHandoffSummary({
manifest: createIniPanelShellViewModel().shellIntegrationManifest,
readiness: createIniPanelShellViewModel().shellIntegrationReadiness,
workflowPlan: createIniPanelShellViewModel().shellIntegrationWorkflowPlan,
}),
);
assert.deepEqual(
createIniPanelShellViewModel().shellSessionHandoffSummarySchema,
createIniPanelShellSessionHandoffSummarySchema(),
);
assert.equal(isSupportedIniPanelShellSessionHandoffSummary(createIniPanelShellViewModel().shellSessionHandoffSummary), true);
assert.deepEqual(
createIniPanelShellViewModel().shellSessionHandoffCompatibilityReport,
createIniPanelShellSessionHandoffCompatibilityReport(createIniPanelShellViewModel().shellSessionHandoffSummary),
);
assert.deepEqual(
createIniPanelShellViewModel().shellSessionHandoffDisplayViewModel,
createIniPanelShellSessionHandoffDisplayViewModel(createIniPanelShellViewModel().shellSessionHandoffCompatibilityReport),
);
assert.deepEqual(
createIniPanelShellViewModel().shellSessionHandoffSnapshot,
createIniPanelShellSessionHandoffSnapshot({
readiness: createIniPanelShellViewModel().shellIntegrationReadiness,
summary: createIniPanelShellViewModel().shellSessionHandoffSummary,
compatibilityReport: createIniPanelShellViewModel().shellSessionHandoffCompatibilityReport,
displayViewModel: createIniPanelShellViewModel().shellSessionHandoffDisplayViewModel,
}),
);
assert.deepEqual(
createIniPanelShellViewModel().shellSessionHandoffRenderState,
createIniPanelShellSessionHandoffRenderState(createIniPanelShellViewModel().shellSessionHandoffSnapshot),
);
assert.deepEqual(
createIniPanelShellViewModel().shellSessionHandoffActionPlan,
createIniPanelShellSessionHandoffActionPlan({
snapshot: createIniPanelShellViewModel().shellSessionHandoffSnapshot,
renderState: createIniPanelShellViewModel().shellSessionHandoffRenderState,
}),
);
assert.deepEqual(
createIniPanelShellViewModel().shellSessionHandoffChecklist,
createIniPanelShellSessionHandoffChecklist({
actionPlan: createIniPanelShellViewModel().shellSessionHandoffActionPlan,
renderState: createIniPanelShellViewModel().shellSessionHandoffRenderState,
}),
);
assert.deepEqual(
createIniPanelShellViewModel().shellSessionHandoffPackage,
createIniPanelShellSessionHandoffPackage({
shellIntegrationManifest: createIniPanelShellViewModel().shellIntegrationManifest,
launchApiManifest: createIniPanelShellViewModel().shellIntegrationManifest.launchApiManifest,
controlPageApiManifest: createIniPanelShellViewModel().shellIntegrationManifest.controlPageApiManifest,
snapshot: createIniPanelShellViewModel().shellSessionHandoffSnapshot,
renderState: createIniPanelShellViewModel().shellSessionHandoffRenderState,
actionPlan: createIniPanelShellViewModel().shellSessionHandoffActionPlan,
checklist: createIniPanelShellViewModel().shellSessionHandoffChecklist,
}),
);
assert.deepEqual(
createIniPanelShellViewModel().shellSessionHandoffPackageSchema,
createIniPanelShellSessionHandoffPackageSchema(),
);
assert.deepEqual(
createIniPanelShellViewModel().shellSessionHandoffPackageReport,
createIniPanelShellSessionHandoffPackageReport(createIniPanelShellViewModel().shellSessionHandoffPackage),
);
assert.deepEqual(
createIniPanelShellViewModel().shellSessionHandoffPackageDisplayViewModel,
createIniPanelShellSessionHandoffPackageDisplayViewModel(
createIniPanelShellViewModel().shellSessionHandoffPackageReport,
),
);
assert.deepEqual(
createIniPanelShellViewModel().shellSessionHandoffPackageRenderState,
createIniPanelShellSessionHandoffPackageRenderState(
createIniPanelShellViewModel().shellSessionHandoffPackageDisplayViewModel,
),
);
assert.deepEqual(
createIniPanelShellViewModel().shellSessionHandoffPackageMountState,
createIniPanelShellSessionHandoffPackageMountState({
handoffPackage: createIniPanelShellViewModel().shellSessionHandoffPackage,
packageRenderState: createIniPanelShellViewModel().shellSessionHandoffPackageRenderState,
}),
);
assert.deepEqual(
createIniPanelShellViewModel().shellSessionHandoffPackageMountDisplayViewModel,
createIniPanelShellSessionHandoffPackageMountDisplayViewModel(
createIniPanelShellViewModel().shellSessionHandoffPackageMountState,
),
);
assert.deepEqual(
createIniPanelShellViewModel().shellSessionHandoffPackageMountRenderState,
createIniPanelShellSessionHandoffPackageMountRenderState(
createIniPanelShellViewModel().shellSessionHandoffPackageMountDisplayViewModel,
),
);
assert.deepEqual(
createIniPanelShellViewModel().shellSessionHandoffMountDomContract,
createIniPanelShellSessionHandoffMountDomContract(),
);
assert.equal(
createIniPanelShellSessionHandoffMountDomReadiness({ documentRef: null }).ready,
false,
);
assert.equal(
mountIniPanelShellSessionHandoff({ documentRef: null }).phase,
"blocked",
);
assert.equal(
mountIniPanelShellSessionHandoffFromControlPage({ documentRef: null }).mountWorkflow.phase,
"blocked",
);
assert.equal(
readIniPanelShellSessionHandoffStatusFromControlPage().phase,
"blocked",
);
assert.equal(
createIniPanelShellSessionHandoffWorkflowSummary().phase,
"blocked",
);
assert.equal(
createIniPanelShellSessionHandoffWorkflowDisplayViewModel().phase,
"blocked",
);
assert.equal(
createIniPanelShellSessionHandoffWorkflowRenderState().phase,
"blocked",
);
assert.equal(
createIniPanelShellSessionHandoffWorkflowDomContract().selectors.mount,
"[data-handoff-workflow]",
);
assert.equal(
createIniPanelShellSessionHandoffWorkflowDomReadiness().phase,
"blocked",
);
assert.equal(
mountIniPanelShellSessionHandoffWorkflow().phase,
"blocked",
);
assert.equal(isSupportedIniPanelShellSessionHandoffPackage(createIniPanelShellViewModel().shellSessionHandoffPackage), true);
assert.equal(isSupportedIniPanelShellIntegrationManifest(createIniPanelShellViewModel().shellIntegrationManifest), true);
assert.deepEqual(createIniPanelShellViewModel().launchApiSchema, createIniPanelLaunchApiSchema());
assert.deepEqual(createIniPanelShellViewModel().launchApiManifest, createIniPanelLaunchApiManifest());
assert.equal(isSupportedIniPanelLaunchApiManifest(createIniPanelShellViewModel().launchApiManifest), true);
assert.equal(createIniPanelShellViewModel().launchApiManifest.manifestVersion, createIniPanelLaunchApiSchema().manifestVersion);
assert.deepEqual(
createIniPanelShellViewModel().controlPage.browserApiSchema,
createControlPageBrowserApiSchema(),
);
assert.deepEqual(
createIniPanelShellViewModel().controlPage.browserApiManifest,
createControlPageBrowserApiManifest(),
);
assert.equal(isSupportedIniPanelControlPageApiManifest(createIniPanelShellViewModel().controlPage.browserApiManifest), true);
assert.deepEqual(
createIniPanelShellViewModel().controlPage.contract,
createIniPanelShellControlPageContract(),
@@ -122,6 +476,7 @@ assert.deepEqual(createControlPageSessionLoadState({ phase: "waiting-panel" }),
controlViewReady: false,
stateBundle: null,
workflowStatus: null,
sessionReadiness: null,
controlView: null,
error: null,
});
@@ -138,28 +493,216 @@ assert.equal(createIniPanelShellViewModel().controlPage.readStatus, readMachineS
assert.equal(createIniPanelShellViewModel().controlPage.refresh, refreshMachineSessionControlPage);
assert.equal(typeof createIniPanelShellViewModel().controlPage.renderView, "function");
assert.match(shellText, /\bexport function createControlPageBrowserApiMethods\b/);
assert.match(shellText, /\bexport function createControlPageBrowserApiManifest\b/);
assert.match(shellText, /\bexport function createControlPageBrowserApiSchema\b/);
assert.match(shellText, /\bexport function createIniPanelLaunchApiSchema\b/);
assert.match(shellText, /\bexport function createIniPanelLaunchApiManifest\b/);
assert.match(shellText, /\bexport function createIniPanelShellIntegrationSchema\b/);
assert.match(shellText, /\bexport function createIniPanelShellIntegrationManifest\b/);
assert.match(shellText, /\bexport function createIniPanelShellIntegrationReadiness\b/);
assert.match(shellText, /\bexport function createIniPanelShellIntegrationWorkflowPlan\b/);
assert.match(shellText, /\bexport function createIniPanelShellSessionHandoffActionPlan\b/);
assert.match(shellText, /\bexport function createIniPanelShellSessionHandoffChecklist\b/);
assert.match(shellText, /\bexport function createIniPanelShellSessionHandoffCompatibilityReport\b/);
assert.match(shellText, /\bexport function createIniPanelShellSessionHandoffDisplayViewModel\b/);
assert.match(shellText, /\bexport function createIniPanelShellSessionHandoffPackage\b/);
assert.match(shellText, /\bexport function createIniPanelShellSessionHandoffPackageReport\b/);
assert.match(shellText, /\bexport function createIniPanelShellSessionHandoffPackageSchema\b/);
assert.match(shellText, /\bexport function createIniPanelShellSessionHandoffRenderState\b/);
assert.match(shellText, /\bexport function createIniPanelShellSessionHandoffSnapshot\b/);
assert.match(shellText, /\bexport function createIniPanelShellSessionHandoffSummary\b/);
assert.match(shellText, /\bexport function createIniPanelShellSessionHandoffSummarySchema\b/);
assert.match(shellText, /\bexport function createIniPanelShellWorkflowOverviewEmbeddingReport\b/);
assert.match(shellText, /\bexport function createIniPanelShellWorkflowOverviewEmbeddingDisplayViewModel\b/);
assert.match(shellText, /\bexport function createIniPanelShellWorkflowOverviewEmbeddingRenderState\b/);
assert.match(shellText, /\bexport function createIniPanelShellWorkflowOverviewEmbeddingDomContract\b/);
assert.match(shellText, /\bexport function createIniPanelShellWorkflowOverviewEmbeddingDomReadiness\b/);
assert.match(shellText, /\bexport function renderIniPanelShellWorkflowOverviewEmbeddingState\b/);
assert.match(shellText, /\bexport function mountIniPanelShellWorkflowOverviewEmbeddingState\b/);
assert.match(shellText, /\bexport function createIniPanelShellWorkflowOverviewEmbeddingMountDisplayViewModel\b/);
assert.match(shellText, /\bexport function createIniPanelShellWorkflowOverviewEmbeddingMountRenderState\b/);
assert.match(shellText, /\bexport function createIniPanelShellWorkflowOverviewEmbeddingMountDomContract\b/);
assert.match(shellText, /\bexport function createIniPanelShellWorkflowOverviewEmbeddingMountDomReadiness\b/);
assert.match(shellText, /\bexport function renderIniPanelShellWorkflowOverviewEmbeddingMountState\b/);
assert.match(shellText, /\bexport function mountIniPanelShellWorkflowOverviewEmbeddingMountState\b/);
assert.match(shellText, /\bexport function isSupportedIniPanelControlPageApiManifest\b/);
assert.match(shellText, /\bexport function isSupportedIniPanelLaunchApiManifest\b/);
assert.match(shellText, /\bexport function isSupportedIniPanelShellIntegrationManifest\b/);
assert.match(shellText, /\bexport function isSupportedIniPanelShellSessionHandoffPackage\b/);
assert.match(shellText, /\bexport function isSupportedIniPanelShellSessionHandoffSummary\b/);
assert.match(controlPageText, /\bisSupportedControlPageApiManifest\b/);
assert.match(controlPageText, /\bgetControlPageApiSchema\b/);
assert.match(controlPageText, /\bgetControlPageApiManifest\b/);
assert.match(controlPageText, /\bgetControlPageApiMethods\b/);
assert.match(launchText, /\blinuxCncIniPanelLaunchApi\b/);
assert.match(launchText, /\bisSupportedLaunchApiManifest\b/);
assert.match(launchText, /\bgetLaunchApiSchema\b/);
assert.match(launchText, /\bgetLaunchApiManifest\b/);
assert.match(launchText, /\bgetShellIntegrationSchema\b/);
assert.match(launchText, /\bgetShellIntegrationManifest\b/);
assert.match(launchText, /\bgetShellIntegrationReadiness\b/);
assert.match(launchText, /\bgetShellSessionHandoffSummarySchema\b/);
assert.match(launchText, /\bgetShellSessionHandoffSummary\b/);
assert.match(launchText, /\bisSupportedShellSessionHandoffSummary\b/);
assert.match(launchText, /\bgetShellSessionHandoffCompatibilityReport\b/);
assert.match(launchText, /\bgetShellSessionHandoffDisplayViewModel\b/);
assert.match(launchText, /\bgetShellSessionHandoffSnapshot\b/);
assert.match(launchText, /\bgetShellSessionHandoffRenderState\b/);
assert.match(launchText, /\bgetShellSessionHandoffActionPlan\b/);
assert.match(launchText, /\bgetShellSessionHandoffChecklist\b/);
assert.match(launchText, /\bgetShellSessionHandoffPackageSchema\b/);
assert.match(launchText, /\bgetShellSessionHandoffPackage\b/);
assert.match(launchText, /\bgetShellSessionHandoffPackageReport\b/);
assert.match(launchText, /\bgetWorkflowOverviewEmbeddingReport\b/);
assert.match(launchText, /\bgetWorkflowOverviewEmbeddingDisplayViewModel\b/);
assert.match(launchText, /\bgetWorkflowOverviewEmbeddingRenderState\b/);
assert.match(launchText, /\bgetWorkflowOverviewEmbeddingDomContract\b/);
assert.match(launchText, /\bgetWorkflowOverviewEmbeddingDomReadiness\b/);
assert.match(launchText, /\brenderWorkflowOverviewEmbeddingState\b/);
assert.match(launchText, /\bmountWorkflowOverviewEmbeddingState\b/);
assert.match(launchText, /\bgetWorkflowOverviewEmbeddingMountDisplayViewModel\b/);
assert.match(launchText, /\bgetWorkflowOverviewEmbeddingMountRenderState\b/);
assert.match(launchText, /\bgetWorkflowOverviewEmbeddingMountDomContract\b/);
assert.match(launchText, /\bgetWorkflowOverviewEmbeddingMountDomReadiness\b/);
assert.match(launchText, /\brenderWorkflowOverviewEmbeddingMountState\b/);
assert.match(launchText, /\bmountWorkflowOverviewEmbeddingMountState\b/);
assert.match(launchText, /\bisSupportedShellSessionHandoffPackage\b/);
assert.match(launchText, /\bdata-handoff-shell\b/);
assert.match(launchText, /\bdata-handoff-status\b/);
assert.match(launchText, /\bdata-handoff-rows\b/);
assert.match(launchText, /\bgetControlPageApiMethods\b/);
assert.match(controlPageText, /\bgetControlPageContract\b/);
assert.match(controlPageText, /\breadControlPageStatus\b/);
assert.match(controlPageText, /\bcreateIniPanelShellControlPageContract\b/);
assert.match(docText, /\blinuxCncIniPanelControlPageApi\.getControlPageApiMethods\b/);
assert.match(docText, /\blinuxCncIniPanelControlPageApi\.getControlPageApiSchema\b/);
assert.match(docText, /\blinuxCncIniPanelControlPageApi\.getControlPageApiManifest\b/);
assert.match(docText, /\blinuxCncIniPanelControlPageApi\.isSupportedControlPageApiManifest\b/);
assert.match(docText, /\blinuxCncIniPanelControlPageApi\.getControlPageContract\b/);
assert.match(docText, /\blinuxCncIniPanelControlPageApi\.readControlPageStatus\b/);
assert.match(docText, /\bcreateControlPageBrowserApiManifest\b/);
assert.match(docText, /\bcreateControlPageBrowserApiSchema\b/);
assert.match(docText, /\bcreateIniPanelLaunchApiManifest\b/);
assert.match(docText, /\bcreateIniPanelLaunchApiSchema\b/);
assert.match(docText, /\bcreateIniPanelShellIntegrationManifest\b/);
assert.match(docText, /\bcreateIniPanelShellIntegrationReadiness\b/);
assert.match(docText, /\bcreateIniPanelShellIntegrationSchema\b/);
assert.match(docText, /\bcreateIniPanelShellIntegrationWorkflowPlan\b/);
assert.match(docText, /\bcreateIniPanelShellSessionHandoffActionPlan\b/);
assert.match(docText, /\bcreateIniPanelShellSessionHandoffChecklist\b/);
assert.match(docText, /\bcreateIniPanelShellSessionHandoffCompatibilityReport\b/);
assert.match(docText, /\bcreateIniPanelShellSessionHandoffDisplayViewModel\b/);
assert.match(docText, /\bcreateIniPanelShellSessionHandoffPackage\b/);
assert.match(docText, /\bcreateIniPanelShellSessionHandoffPackageReport\b/);
assert.match(docText, /\bcreateIniPanelShellSessionHandoffPackageSchema\b/);
assert.match(docText, /\bcreateIniPanelShellSessionHandoffRenderState\b/);
assert.match(docText, /\bcreateIniPanelShellSessionHandoffSnapshot\b/);
assert.match(docText, /\bcreateIniPanelShellSessionHandoffSummary\b/);
assert.match(docText, /\bcreateIniPanelShellSessionHandoffSummarySchema\b/);
assert.match(docText, /\bcreateIniPanelShellWorkflowOverviewEmbeddingReport\b/);
assert.match(docText, /\bcreateIniPanelShellWorkflowOverviewEmbeddingDisplayViewModel\b/);
assert.match(docText, /\bcreateIniPanelShellWorkflowOverviewEmbeddingRenderState\b/);
assert.match(docText, /\bcreateIniPanelShellWorkflowOverviewEmbeddingDomContract\b/);
assert.match(docText, /\bcreateIniPanelShellWorkflowOverviewEmbeddingDomReadiness\b/);
assert.match(docText, /\brenderIniPanelShellWorkflowOverviewEmbeddingState\b/);
assert.match(docText, /\bmountIniPanelShellWorkflowOverviewEmbeddingState\b/);
assert.match(docText, /\bcreateIniPanelShellWorkflowOverviewEmbeddingMountDisplayViewModel\b/);
assert.match(docText, /\bcreateIniPanelShellWorkflowOverviewEmbeddingMountRenderState\b/);
assert.match(docText, /\bcreateIniPanelShellWorkflowOverviewEmbeddingMountDomContract\b/);
assert.match(docText, /\bcreateIniPanelShellWorkflowOverviewEmbeddingMountDomReadiness\b/);
assert.match(docText, /\brenderIniPanelShellWorkflowOverviewEmbeddingMountState\b/);
assert.match(docText, /\bmountIniPanelShellWorkflowOverviewEmbeddingMountState\b/);
assert.match(docText, /\bisSupportedIniPanelControlPageApiManifest\b/);
assert.match(docText, /\bisSupportedIniPanelLaunchApiManifest\b/);
assert.match(docText, /\bisSupportedIniPanelShellIntegrationManifest\b/);
assert.match(docText, /\bisSupportedIniPanelShellSessionHandoffPackage\b/);
assert.match(docText, /\bisSupportedIniPanelShellSessionHandoffSummary\b/);
assert.match(docText, /\blinuxCncIniPanelLaunchApi\.isSupportedLaunchApiManifest\b/);
assert.match(docText, /\blinuxCncIniPanelLaunchApi\.getLaunchApiSchema\b/);
assert.match(docText, /\blinuxCncIniPanelLaunchApi\.getLaunchApiManifest\b/);
assert.match(docText, /\blinuxCncIniPanelLaunchApi\.getShellIntegrationSchema\b/);
assert.match(docText, /\blinuxCncIniPanelLaunchApi\.getShellIntegrationManifest\b/);
assert.match(docText, /\blinuxCncIniPanelLaunchApi\.getShellIntegrationReadiness\b/);
assert.match(docText, /\blinuxCncIniPanelLaunchApi\.getShellSessionHandoffSummarySchema\b/);
assert.match(docText, /\blinuxCncIniPanelLaunchApi\.getShellSessionHandoffSummary\b/);
assert.match(docText, /\blinuxCncIniPanelLaunchApi\.isSupportedShellSessionHandoffSummary\b/);
assert.match(docText, /\blinuxCncIniPanelLaunchApi\.getShellSessionHandoffCompatibilityReport\b/);
assert.match(docText, /\blinuxCncIniPanelLaunchApi\.getShellSessionHandoffDisplayViewModel\b/);
assert.match(docText, /\blinuxCncIniPanelLaunchApi\.getShellSessionHandoffSnapshot\b/);
assert.match(docText, /\blinuxCncIniPanelLaunchApi\.getShellSessionHandoffRenderState\b/);
assert.match(docText, /\blinuxCncIniPanelLaunchApi\.getShellSessionHandoffActionPlan\b/);
assert.match(docText, /\blinuxCncIniPanelLaunchApi\.getShellSessionHandoffChecklist\b/);
assert.match(docText, /\blinuxCncIniPanelLaunchApi\.getShellSessionHandoffPackageSchema\b/);
assert.match(docText, /\blinuxCncIniPanelLaunchApi\.getShellSessionHandoffPackage\b/);
assert.match(docText, /\blinuxCncIniPanelLaunchApi\.getShellSessionHandoffPackageReport\b/);
assert.match(docText, /\blinuxCncIniPanelLaunchApi\.getWorkflowOverviewEmbeddingReport\b/);
assert.match(docText, /\blinuxCncIniPanelLaunchApi\.getWorkflowOverviewEmbeddingDisplayViewModel\b/);
assert.match(docText, /\blinuxCncIniPanelLaunchApi\.getWorkflowOverviewEmbeddingRenderState\b/);
assert.match(docText, /\blinuxCncIniPanelLaunchApi\.getWorkflowOverviewEmbeddingDomContract\b/);
assert.match(docText, /\blinuxCncIniPanelLaunchApi\.getWorkflowOverviewEmbeddingDomReadiness\b/);
assert.match(docText, /\blinuxCncIniPanelLaunchApi\.renderWorkflowOverviewEmbeddingState\b/);
assert.match(docText, /\blinuxCncIniPanelLaunchApi\.mountWorkflowOverviewEmbeddingState\b/);
assert.match(docText, /\blinuxCncIniPanelLaunchApi\.getWorkflowOverviewEmbeddingMountDisplayViewModel\b/);
assert.match(docText, /\blinuxCncIniPanelLaunchApi\.getWorkflowOverviewEmbeddingMountRenderState\b/);
assert.match(docText, /\blinuxCncIniPanelLaunchApi\.getWorkflowOverviewEmbeddingMountDomContract\b/);
assert.match(docText, /\blinuxCncIniPanelLaunchApi\.getWorkflowOverviewEmbeddingMountDomReadiness\b/);
assert.match(docText, /\blinuxCncIniPanelLaunchApi\.renderWorkflowOverviewEmbeddingMountState\b/);
assert.match(docText, /\blinuxCncIniPanelLaunchApi\.mountWorkflowOverviewEmbeddingMountState\b/);
assert.match(docText, /\blinuxCncIniPanelLaunchApi\.isSupportedShellSessionHandoffPackage\b/);
assert.match(docText, /\bdata-handoff-shell\b/);
assert.match(docText, /\bdata-handoff-status\b/);
assert.match(docText, /\bdata-handoff-rows\b/);
assert.match(docText, /\bdata-external-shell-mount\b/);
assert.match(docText, /\brenderIniPanelShellSessionHandoffMountState\b/);
assert.match(docText, /\blinuxCncIniPanelLaunchApi\.renderShellSessionHandoffMountState\b/);
assert.match(docText, /\blinuxCncIniPanelLaunchApi\.getShellSessionHandoffMountDomContract\b/);
assert.match(docText, /\blinuxCncIniPanelLaunchApi\.getShellSessionHandoffMountDomReadiness\b/);
assert.match(docText, /\blinuxCncIniPanelLaunchApi\.mountShellSessionHandoff\b/);
assert.match(docText, /\blinuxCncIniPanelLaunchApi\.mountShellSessionHandoffFromControlPage\b/);
assert.match(docText, /\blinuxCncIniPanelLaunchApi\.readShellSessionHandoffStatusFromControlPage\b/);
assert.match(docText, /\blinuxCncIniPanelLaunchApi\.getShellSessionHandoffWorkflowSummary\b/);
assert.match(docText, /\blinuxCncIniPanelLaunchApi\.getShellSessionHandoffWorkflowDisplayViewModel\b/);
assert.match(docText, /\blinuxCncIniPanelLaunchApi\.getShellSessionHandoffWorkflowRenderState\b/);
assert.match(docText, /\blinuxCncIniPanelLaunchApi\.getShellSessionHandoffWorkflowDomContract\b/);
assert.match(docText, /\blinuxCncIniPanelLaunchApi\.getShellSessionHandoffWorkflowDomReadiness\b/);
assert.match(docText, /\blinuxCncIniPanelLaunchApi\.renderShellSessionHandoffWorkflowState\b/);
assert.match(docText, /\blinuxCncIniPanelLaunchApi\.mountShellSessionHandoffWorkflow\b/);
assert.match(docText, /\blinuxCncIniPanelLaunchApi\.getControlPageApiMethods\b/);
assert.match(docText, /\bini_panel_shell_integration_workflow_smoke\.html\b/);
assert.match(browserIniPanelText, /\bini_panel_shell_integration_workflow_smoke\.html\b/);
assert.match(shellIntegrationWorkflowSmokeText, /\bgetShellIntegrationReadiness\b/);
assert.match(shellIntegrationWorkflowSmokeText, /\bgetShellSessionHandoffSummary\b/);
assert.match(shellIntegrationWorkflowSmokeText, /\bisSupportedShellSessionHandoffSummary\b/);
assert.match(shellIntegrationWorkflowSmokeText, /\bgetShellSessionHandoffCompatibilityReport\b/);
assert.match(shellIntegrationWorkflowSmokeText, /\bgetShellSessionHandoffDisplayViewModel\b/);
assert.match(shellIntegrationWorkflowSmokeText, /\bgetShellSessionHandoffSnapshot\b/);
assert.match(shellIntegrationWorkflowSmokeText, /\bgetShellSessionHandoffRenderState\b/);
assert.match(shellIntegrationWorkflowSmokeText, /\bgetShellSessionHandoffActionPlan\b/);
assert.match(shellIntegrationWorkflowSmokeText, /\bgetShellSessionHandoffChecklist\b/);
assert.match(shellIntegrationWorkflowSmokeText, /\bgetShellSessionHandoffPackageSchema\b/);
assert.match(shellIntegrationWorkflowSmokeText, /\bgetShellSessionHandoffPackage\b/);
assert.match(shellIntegrationWorkflowSmokeText, /\bgetShellSessionHandoffPackageReport\b/);
assert.match(shellIntegrationWorkflowSmokeText, /\bgetShellSessionHandoffPackageMountRenderState\b/);
assert.match(shellIntegrationWorkflowSmokeText, /\bgetShellSessionHandoffMountDomContract\b/);
assert.match(shellIntegrationWorkflowSmokeText, /\bgetShellSessionHandoffMountDomReadiness\b/);
assert.match(shellIntegrationWorkflowSmokeText, /\bmountShellSessionHandoff\b/);
assert.match(shellIntegrationWorkflowSmokeText, /\bmountShellSessionHandoffFromControlPage\b/);
assert.match(shellIntegrationWorkflowSmokeText, /\breadShellSessionHandoffStatusFromControlPage\b/);
assert.match(shellIntegrationWorkflowSmokeText, /\bgetShellSessionHandoffWorkflowSummary\b/);
assert.match(shellIntegrationWorkflowSmokeText, /\bgetShellSessionHandoffWorkflowDisplayViewModel\b/);
assert.match(shellIntegrationWorkflowSmokeText, /\bgetShellSessionHandoffWorkflowRenderState\b/);
assert.match(shellIntegrationWorkflowSmokeText, /\bgetShellSessionHandoffWorkflowDomContract\b/);
assert.match(shellIntegrationWorkflowSmokeText, /\bgetShellSessionHandoffWorkflowDomReadiness\b/);
assert.match(shellIntegrationWorkflowSmokeText, /\brenderShellSessionHandoffWorkflowState\b/);
assert.match(shellIntegrationWorkflowSmokeText, /\bmountShellSessionHandoffWorkflow\b/);
assert.match(shellIntegrationWorkflowSmokeText, /\bgetWorkflowOverviewEmbeddingReport\b/);
assert.match(shellIntegrationWorkflowSmokeText, /\brenderShellSessionHandoffMountState\b/);
assert.match(shellIntegrationWorkflowSmokeText, /\bdata-external-shell-mount\b/);
assert.match(shellIntegrationWorkflowSmokeText, /\bdata-external-mount-value\b/);
assert.match(shellIntegrationWorkflowSmokeText, /\bisSupportedShellSessionHandoffPackage\b/);
assert.match(shellIntegrationWorkflowSmokeText, /\bhandoffSummary\.handoffMethods\.readStatus\b/);
assert.match(shellIntegrationWorkflowSmokeText, /\breadControlPageStatus\b/);
assert.match(shellIntegrationWorkflowSmokeText, /\bbrowser_ini_shell_integration_workflow_smoke=ok\b/);
assert.match(docText, /\bOPFS\/session persistence capabilities\b/);
assert.match(docText, /\bmachine files\b/);
assert.match(docText, /\bsession snapshots\b/);

View File

@@ -17,6 +17,11 @@ assert.deepEqual(INI_PANEL_ENTRIES, [
title: "Open read-only control view",
href: "./control-page.html",
},
{
id: "workflow-overview",
title: "Open shell handoff overview",
href: "./workflow-overview.html",
},
]);
const manifest = getIniPanelEntryManifest();
@@ -26,6 +31,7 @@ assert.notStrictEqual(manifest[0], INI_PANEL_ENTRIES[0]);
assert.deepEqual(getIniPanelEntryByHref("./index.html"), INI_PANEL_ENTRIES[0]);
assert.deepEqual(getIniPanelEntryByHref("./control-page.html"), INI_PANEL_ENTRIES[1]);
assert.deepEqual(getIniPanelEntryByHref("./workflow-overview.html"), INI_PANEL_ENTRIES[2]);
assert.equal(getIniPanelEntryByHref("./missing.html"), null);
manifest[0].title = "changed";

View File

@@ -8,6 +8,7 @@ import {
createControlPageBrowserApiMethods,
createIniPanelLaunchApiManifest,
createIniPanelLaunchApiSchema,
createIniPanelShellIntegrationManifest,
createIniPanelShellViewModel,
getVisibleIniPanelEntries,
isSupportedIniPanelLaunchApiManifest,
@@ -42,6 +43,89 @@ assert.deepEqual(manifest, {
"isSupportedLaunchApiManifest",
"getLaunchApiSchema",
"getLaunchApiManifest",
"getShellIntegrationSchema",
"getShellIntegrationManifest",
"getShellIntegrationReadiness",
"getShellSessionHandoffSummarySchema",
"getShellSessionHandoffSummary",
"isSupportedShellSessionHandoffSummary",
"getShellSessionHandoffCompatibilityReport",
"getShellSessionHandoffDisplayViewModel",
"getShellSessionHandoffSnapshot",
"getShellSessionHandoffRenderState",
"getShellSessionHandoffActionPlan",
"getShellSessionHandoffChecklist",
"getShellSessionHandoffPackageSchema",
"getShellSessionHandoffPackage",
"getShellSessionHandoffPackageReport",
"getShellSessionHandoffPackageDisplayViewModel",
"getShellSessionHandoffPackageRenderState",
"getShellSessionHandoffPackageMountState",
"getShellSessionHandoffPackageMountDisplayViewModel",
"getShellSessionHandoffPackageMountRenderState",
"getShellSessionHandoffMountDomContract",
"getShellSessionHandoffMountDomReadiness",
"renderShellSessionHandoffMountState",
"mountShellSessionHandoff",
"mountShellSessionHandoffFromControlPage",
"readShellSessionHandoffStatusFromControlPage",
"getMachineSessionPersistenceSummary",
"getShellSessionHandoffWorkflowSummary",
"getShellSessionReadinessWorkflowReport",
"getShellApiSurfaceInventory",
"getShellSessionHandoffWorkflowDisplayViewModel",
"getShellSessionHandoffWorkflowRenderState",
"getShellSessionHandoffWorkflowDomContract",
"getShellSessionHandoffWorkflowDomReadiness",
"renderShellSessionHandoffWorkflowState",
"mountShellSessionHandoffWorkflow",
"getWorkflowOverviewContract",
"getWorkflowOverviewReadiness",
"mountWorkflowOverviewEmbedding",
"runWorkflowOverviewReleaseReadinessArtifactJsonWorkflow",
"getWorkflowOverviewReleaseReadinessArtifactJsonWorkflowSummaryViewModel",
"getWorkflowOverviewReleaseReadinessArtifactJsonWorkflowActionPlan",
"getWorkflowOverviewReleaseReadinessArtifactJsonWorkflowRenderState",
"getWorkflowOverviewReleaseReadinessArtifactJsonWorkflowDomContract",
"getWorkflowOverviewReleaseReadinessArtifactJsonWorkflowDomReadiness",
"renderWorkflowOverviewReleaseReadinessArtifactJsonWorkflowState",
"mountWorkflowOverviewReleaseReadinessArtifactJsonWorkflowState",
"loadWorkflowOverviewReleaseReadinessArtifactUrl",
"getWorkflowOverviewReleaseReadinessArtifactUrlWorkflowSummaryViewModel",
"getWorkflowOverviewReleaseReadinessArtifactUrlWorkflowActionPlan",
"getWorkflowOverviewReleaseReadinessArtifactUrlWorkflowRenderState",
"getWorkflowOverviewReleaseReadinessArtifactUrlWorkflowDomContract",
"getWorkflowOverviewReleaseReadinessArtifactUrlWorkflowDomReadiness",
"renderWorkflowOverviewReleaseReadinessArtifactUrlWorkflowState",
"mountWorkflowOverviewReleaseReadinessArtifactUrlWorkflowState",
"getWorkflowOverviewReleaseReadinessArtifactValidationSummaryViewModel",
"getWorkflowOverviewReleaseReadinessArtifactValidationActionPlan",
"getWorkflowOverviewReleaseReadinessArtifactValidationActionPlanRenderState",
"getWorkflowOverviewReleaseReadinessArtifactValidationActionPlanDomContract",
"getWorkflowOverviewReleaseReadinessArtifactValidationActionPlanDomReadiness",
"renderWorkflowOverviewReleaseReadinessArtifactValidationActionPlanState",
"mountWorkflowOverviewReleaseReadinessArtifactValidationActionPlanState",
"getWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryViewModel",
"getWorkflowOverviewReleaseReadinessArtifactUrlGateExecutionSummaryViewModel",
"getWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryRenderState",
"getWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryDomContract",
"getWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryDomReadiness",
"renderWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryState",
"mountWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryState",
"getWorkflowOverviewEmbeddingReport",
"getWorkflowOverviewEmbeddingDisplayViewModel",
"getWorkflowOverviewEmbeddingRenderState",
"getWorkflowOverviewEmbeddingDomContract",
"getWorkflowOverviewEmbeddingDomReadiness",
"renderWorkflowOverviewEmbeddingState",
"mountWorkflowOverviewEmbeddingState",
"getWorkflowOverviewEmbeddingMountDisplayViewModel",
"getWorkflowOverviewEmbeddingMountRenderState",
"getWorkflowOverviewEmbeddingMountDomContract",
"getWorkflowOverviewEmbeddingMountDomReadiness",
"renderWorkflowOverviewEmbeddingMountState",
"mountWorkflowOverviewEmbeddingMountState",
"isSupportedShellSessionHandoffPackage",
"getLauncherLinks",
"getControlPageApiMethods",
],
@@ -56,6 +140,11 @@ assert.deepEqual(manifest, {
href: "./control-page.html",
title: "Open read-only control view",
},
{
id: "workflow-overview",
href: "./workflow-overview.html",
title: "Open shell handoff overview",
},
],
targetPages: [
{
@@ -66,6 +155,10 @@ assert.deepEqual(manifest, {
entryId: "control-view",
href: "./control-page.html",
},
{
entryId: "workflow-overview",
href: "./workflow-overview.html",
},
],
controlPageApiMethods: createControlPageBrowserApiMethods(),
persistenceCapabilities: [
@@ -89,6 +182,10 @@ assert.deepEqual(manifest, {
assert.deepEqual(createIniPanelShellViewModel().launchApiManifest, manifest);
assert.deepEqual(createIniPanelShellViewModel().launchApiSchema, schema);
assert.deepEqual(
createIniPanelShellViewModel().shellIntegrationManifest,
createIniPanelShellIntegrationManifest(),
);
assert.equal(createIniPanelShellViewModel().launchApiManifest.manifestVersion, schema.manifestVersion);
assert.equal(isSupportedIniPanelLaunchApiManifest(manifest), true);
assert.equal(isSupportedIniPanelLaunchApiManifest({ ...manifest, manifestVersion: 2 }), false);
@@ -135,6 +232,95 @@ assert.match(shellText, /\bexport function isSupportedIniPanelLaunchApiManifest\
assert.match(launchText, /\bisSupportedLaunchApiManifest\b/);
assert.match(launchText, /\bgetLaunchApiSchema\b/);
assert.match(launchText, /\bgetLaunchApiManifest\b/);
assert.match(launchText, /\bgetShellIntegrationSchema\b/);
assert.match(launchText, /\bgetShellIntegrationManifest\b/);
assert.match(launchText, /\bgetShellIntegrationReadiness\b/);
assert.match(launchText, /\bgetShellSessionHandoffSummarySchema\b/);
assert.match(launchText, /\bgetShellSessionHandoffSummary\b/);
assert.match(launchText, /\bisSupportedShellSessionHandoffSummary\b/);
assert.match(launchText, /\bgetShellSessionHandoffCompatibilityReport\b/);
assert.match(launchText, /\bgetShellSessionHandoffDisplayViewModel\b/);
assert.match(launchText, /\bgetShellSessionHandoffSnapshot\b/);
assert.match(launchText, /\bgetShellSessionHandoffRenderState\b/);
assert.match(launchText, /\bgetShellSessionHandoffActionPlan\b/);
assert.match(launchText, /\bgetShellSessionHandoffChecklist\b/);
assert.match(launchText, /\bgetShellSessionHandoffPackageSchema\b/);
assert.match(launchText, /\bgetShellSessionHandoffPackage\b/);
assert.match(launchText, /\bgetShellSessionHandoffPackageReport\b/);
assert.match(launchText, /\bgetShellSessionHandoffPackageDisplayViewModel\b/);
assert.match(launchText, /\bgetShellSessionHandoffPackageRenderState\b/);
assert.match(launchText, /\bgetShellSessionHandoffPackageMountState\b/);
assert.match(launchText, /\bgetShellSessionHandoffPackageMountDisplayViewModel\b/);
assert.match(launchText, /\bgetShellSessionHandoffPackageMountRenderState\b/);
assert.match(launchText, /\bgetShellSessionHandoffMountDomContract\b/);
assert.match(launchText, /\bgetShellSessionHandoffMountDomReadiness\b/);
assert.match(launchText, /\brenderShellSessionHandoffMountState\b/);
assert.match(launchText, /\bmountShellSessionHandoff\b/);
assert.match(launchText, /\bmountShellSessionHandoffFromControlPage\b/);
assert.match(launchText, /\breadShellSessionHandoffStatusFromControlPage\b/);
assert.match(launchText, /\bgetMachineSessionPersistenceSummary\b/);
assert.match(launchText, /\bgetShellSessionHandoffWorkflowSummary\b/);
assert.match(launchText, /\bgetShellSessionReadinessWorkflowReport\b/);
assert.match(launchText, /\bgetShellApiSurfaceInventory\b/);
assert.match(launchText, /\bgetShellSessionHandoffWorkflowDisplayViewModel\b/);
assert.match(launchText, /\bgetShellSessionHandoffWorkflowRenderState\b/);
assert.match(launchText, /\bgetShellSessionHandoffWorkflowDomContract\b/);
assert.match(launchText, /\bgetShellSessionHandoffWorkflowDomReadiness\b/);
assert.match(launchText, /\brenderShellSessionHandoffWorkflowState\b/);
assert.match(launchText, /\bmountShellSessionHandoffWorkflow\b/);
assert.match(launchText, /\bgetWorkflowOverviewContract\b/);
assert.match(launchText, /\bgetWorkflowOverviewReadiness\b/);
assert.match(launchText, /\bmountWorkflowOverviewEmbedding\b/);
assert.match(launchText, /\brunWorkflowOverviewReleaseReadinessArtifactJsonWorkflow\b/);
assert.match(launchText, /\bgetWorkflowOverviewReleaseReadinessArtifactJsonWorkflowSummaryViewModel\b/);
assert.match(launchText, /\bgetWorkflowOverviewReleaseReadinessArtifactJsonWorkflowActionPlan\b/);
assert.match(launchText, /\bgetWorkflowOverviewReleaseReadinessArtifactJsonWorkflowRenderState\b/);
assert.match(launchText, /\bgetWorkflowOverviewReleaseReadinessArtifactJsonWorkflowDomContract\b/);
assert.match(launchText, /\bgetWorkflowOverviewReleaseReadinessArtifactJsonWorkflowDomReadiness\b/);
assert.match(launchText, /\brenderWorkflowOverviewReleaseReadinessArtifactJsonWorkflowState\b/);
assert.match(launchText, /\bmountWorkflowOverviewReleaseReadinessArtifactJsonWorkflowState\b/);
assert.match(launchText, /\bloadWorkflowOverviewReleaseReadinessArtifactUrl\b/);
assert.match(launchText, /\bgetWorkflowOverviewReleaseReadinessArtifactUrlWorkflowSummaryViewModel\b/);
assert.match(launchText, /\bgetWorkflowOverviewReleaseReadinessArtifactUrlWorkflowActionPlan\b/);
assert.match(launchText, /\bgetWorkflowOverviewReleaseReadinessArtifactUrlWorkflowRenderState\b/);
assert.match(launchText, /\bgetWorkflowOverviewReleaseReadinessArtifactUrlWorkflowDomContract\b/);
assert.match(launchText, /\bgetWorkflowOverviewReleaseReadinessArtifactUrlWorkflowDomReadiness\b/);
assert.match(launchText, /\brenderWorkflowOverviewReleaseReadinessArtifactUrlWorkflowState\b/);
assert.match(launchText, /\bmountWorkflowOverviewReleaseReadinessArtifactUrlWorkflowState\b/);
assert.match(launchText, /\bgetWorkflowOverviewReleaseReadinessArtifactValidationSummaryViewModel\b/);
assert.match(launchText, /\bgetWorkflowOverviewReleaseReadinessArtifactValidationActionPlan\b/);
assert.match(launchText, /\bgetWorkflowOverviewReleaseReadinessArtifactValidationActionPlanRenderState\b/);
assert.match(launchText, /\bgetWorkflowOverviewReleaseReadinessArtifactValidationActionPlanDomContract\b/);
assert.match(launchText, /\bgetWorkflowOverviewReleaseReadinessArtifactValidationActionPlanDomReadiness\b/);
assert.match(launchText, /\brenderWorkflowOverviewReleaseReadinessArtifactValidationActionPlanState\b/);
assert.match(launchText, /\bmountWorkflowOverviewReleaseReadinessArtifactValidationActionPlanState\b/);
assert.match(launchText, /\bgetWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryViewModel\b/);
assert.match(launchText, /\bgetWorkflowOverviewReleaseReadinessArtifactUrlGateExecutionSummaryViewModel\b/);
assert.match(launchText, /\bgetWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryRenderState\b/);
assert.match(launchText, /\bgetWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryDomContract\b/);
assert.match(launchText, /\bgetWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryDomReadiness\b/);
assert.match(launchText, /\brenderWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryState\b/);
assert.match(launchText, /\bmountWorkflowOverviewReleaseReadinessArtifactGateExecutionSummaryState\b/);
assert.match(launchText, /\bgetWorkflowOverviewEmbeddingReport\b/);
assert.match(launchText, /\bgetWorkflowOverviewEmbeddingDisplayViewModel\b/);
assert.match(launchText, /\bgetWorkflowOverviewEmbeddingRenderState\b/);
assert.match(launchText, /\bgetWorkflowOverviewEmbeddingDomContract\b/);
assert.match(launchText, /\bgetWorkflowOverviewEmbeddingDomReadiness\b/);
assert.match(launchText, /\brenderWorkflowOverviewEmbeddingState\b/);
assert.match(launchText, /\bmountWorkflowOverviewEmbeddingState\b/);
assert.match(launchText, /\bgetWorkflowOverviewEmbeddingMountDisplayViewModel\b/);
assert.match(launchText, /\bgetWorkflowOverviewEmbeddingMountRenderState\b/);
assert.match(launchText, /\bgetWorkflowOverviewEmbeddingMountDomContract\b/);
assert.match(launchText, /\bgetWorkflowOverviewEmbeddingMountDomReadiness\b/);
assert.match(launchText, /\brenderWorkflowOverviewEmbeddingMountState\b/);
assert.match(launchText, /\bmountWorkflowOverviewEmbeddingMountState\b/);
assert.match(launchText, /\bisSupportedShellSessionHandoffPackage\b/);
assert.match(launchText, /\bdata-handoff-workflow\b/);
assert.match(launchText, /\bdata-handoff-workflow-status\b/);
assert.match(launchText, /\bdata-handoff-workflow-rows\b/);
assert.match(launchText, /\bdata-handoff-mount\b/);
assert.match(launchText, /\bdata-handoff-mount-status\b/);
assert.match(launchText, /\bdata-handoff-mount-rows\b/);
assert.match(launchText, /\blinuxCncIniPanelLaunchApi\b/);
console.log("ini_panel_launch_api_manifest_node_smoke=ok");

View File

@@ -30,6 +30,11 @@ const stateBundle = {
};
const workflowStatus = {
overview: stateBundle.overview,
sessionReadiness: {
phase: "ready",
ready: true,
missing: [],
},
runReadiness: stateBundle.report.runReadiness,
session: stateBundle.report.session,
run: stateBundle.report.run,
@@ -56,6 +61,7 @@ assert.deepEqual(
overview: stateBundle.overview,
controlView,
report: stateBundle.report,
sessionReadiness: workflowStatus.sessionReadiness,
runReadiness: stateBundle.report.runReadiness,
run: stateBundle.report.run,
session: stateBundle.report.session,
@@ -73,6 +79,7 @@ assert.deepEqual(createMachineSessionReadonlyStatus(), {
overview: null,
controlView: null,
report: null,
sessionReadiness: null,
runReadiness: null,
run: null,
session: null,
@@ -103,6 +110,7 @@ assert.deepEqual(createMachineSessionReadonlyStatusApiManifest(), {
"overview",
"controlView",
"report",
"sessionReadiness",
"runReadiness",
"run",
"session",

View File

@@ -1,9 +1,12 @@
import assert from "node:assert/strict";
import {
createMachineSessionReadinessFieldState,
createMachineSessionLoadFieldState,
loadMachineSessionIntoWasmWorkflow,
machineSessionLoadWorkflowLogLines,
machineSessionReadinessLogLines,
readMachineSessionReadinessWorkflow,
restoreMachineSessionSnapshotWorkflow,
saveMachineSessionSnapshotWorkflow,
} from "../../../runtime/ui/ini-panel/session-workflow.js";
@@ -63,6 +66,101 @@ await assert.rejects(
/machine session loader/,
);
const readySessionReadiness = {
ready: true,
phase: "ready",
missing: [],
checks: [
{
name: "ini",
path: "linuxcnc/machines/xyzab-tdr/machine.ini",
ok: true,
error: null,
},
{
name: "parameters",
path: "linuxcnc/machines/xyzab-tdr/linuxcnc.var",
ok: true,
error: null,
},
{
name: "toolTable",
path: "linuxcnc/machines/xyzab-tdr/tool.tbl",
ok: true,
error: null,
},
],
snapshotCheck: {
name: "snapshot",
path: "linuxcnc/sessions/ui-machine-session/ui-machine-session.json",
ok: true,
error: null,
},
};
const readinessWorkflowCalls = [];
const readinessWorkflow = await readMachineSessionReadinessWorkflow({
readReadiness: async () => {
readinessWorkflowCalls.push(["readReadiness"]);
return readySessionReadiness;
},
});
assert.deepEqual(readinessWorkflowCalls, [["readReadiness"]]);
assert.equal(readinessWorkflow.readiness, readySessionReadiness);
assert.deepEqual(createMachineSessionReadinessFieldState(readySessionReadiness), {
sessionReadinessPhase: "ready",
sessionReadinessReady: "ready",
sessionReadinessMissing: "none",
});
assert.deepEqual(readinessWorkflow.fields, {
sessionReadinessPhase: "ready",
sessionReadinessReady: "ready",
sessionReadinessMissing: "none",
});
assert.deepEqual(machineSessionReadinessLogLines(readySessionReadiness), [
"Session readiness: ready",
"ini: linuxcnc/machines/xyzab-tdr/machine.ini -> ok",
"parameters: linuxcnc/machines/xyzab-tdr/linuxcnc.var -> ok",
"toolTable: linuxcnc/machines/xyzab-tdr/tool.tbl -> ok",
"snapshot: linuxcnc/sessions/ui-machine-session/ui-machine-session.json -> ok",
]);
assert.deepEqual(readinessWorkflow.logLines, machineSessionReadinessLogLines(readySessionReadiness));
await assert.rejects(
() => readMachineSessionReadinessWorkflow({}),
/machine session readiness reader/,
);
const blockedSessionReadiness = {
ready: false,
phase: "blocked",
missing: ["parameters", "toolTable"],
checks: [
{
name: "parameters",
path: "linuxcnc/machines/missing/linuxcnc.var",
ok: false,
error: "missing file",
},
{
name: "toolTable",
path: "linuxcnc/machines/missing/tool.tbl",
ok: false,
error: "missing file",
},
],
snapshotCheck: null,
};
assert.deepEqual(createMachineSessionReadinessFieldState(blockedSessionReadiness), {
sessionReadinessPhase: "blocked",
sessionReadinessReady: "blocked",
sessionReadinessMissing: "parameters, toolTable",
});
assert.deepEqual(machineSessionReadinessLogLines(blockedSessionReadiness), [
"Session readiness: blocked",
"parameters: linuxcnc/machines/missing/linuxcnc.var -> blocked (missing file)",
"toolTable: linuxcnc/machines/missing/tool.tbl -> blocked (missing file)",
]);
const calls = [];
const saveWorkflow = await saveMachineSessionSnapshotWorkflow({
machineId: "xyzab-tdr",
@@ -224,6 +322,75 @@ assert.deepEqual(restoreWorkflow.logLines, [
"G-code: linuxcnc/gcode/ui-session.ngc",
]);
const restoreReadinessCalls = [];
const restoreReadinessWorkflow = await restoreMachineSessionSnapshotWorkflow({
machineId: "xyzab-tdr",
snapshotId: "ui-machine-session",
snapshotFilename: "ui-machine-session.json",
snapshotLabel: "ui-machine-session/ui-machine-session.json",
loadMachineSessionSnapshot: async (snapshotId, machineId, options) => {
restoreReadinessCalls.push(["loadMachineSessionSnapshot", snapshotId, machineId, options]);
return {
sessionId: snapshotId,
metadata: {
workflow: "save-session-snapshot",
snapshotLabel: "ui-machine-session/ui-machine-session.json",
defaultGcodeOpfsPath: "linuxcnc/gcode/ui-session.ngc",
},
payload: {
files: {
ini: "linuxcnc/machines/xyzab-tdr/machine.ini",
parameters: "linuxcnc/machines/xyzab-tdr/linuxcnc.var",
toolTable: "linuxcnc/machines/xyzab-tdr/tool.tbl",
gcode: "linuxcnc/gcode/ui-session.ngc",
},
},
};
},
loadMachineTextFiles: async (machineId) => {
restoreReadinessCalls.push(["loadMachineTextFiles", machineId]);
return {
ini: "[EMC]\nMACHINE = xyzab-tdr\n",
};
},
readReadiness: async () => {
restoreReadinessCalls.push(["readReadiness"]);
return readySessionReadiness;
},
});
assert.deepEqual(restoreReadinessCalls, [
[
"loadMachineSessionSnapshot",
"ui-machine-session",
"xyzab-tdr",
{ filename: "ui-machine-session.json" },
],
["loadMachineTextFiles", "xyzab-tdr"],
["readReadiness"],
]);
assert.equal(restoreReadinessWorkflow.readiness, readySessionReadiness);
assert.deepEqual(restoreReadinessWorkflow.fields, {
sessionIni: "linuxcnc/machines/xyzab-tdr/machine.ini",
sessionParameters: "linuxcnc/machines/xyzab-tdr/linuxcnc.var",
sessionToolTable: "linuxcnc/machines/xyzab-tdr/tool.tbl",
sessionGcode: "linuxcnc/gcode/ui-session.ngc",
sessionSnapshot: "ui-machine-session/ui-machine-session.json",
sessionReadinessPhase: "ready",
sessionReadinessReady: "ready",
sessionReadinessMissing: "none",
});
assert.deepEqual(restoreReadinessWorkflow.logLines, [
"Snapshot: ui-machine-session/ui-machine-session.json",
"Workflow: save-session-snapshot",
"Default G-code: linuxcnc/gcode/ui-session.ngc",
"INI: linuxcnc/machines/xyzab-tdr/machine.ini",
"Parameter file: linuxcnc/machines/xyzab-tdr/linuxcnc.var",
"Tool table file: linuxcnc/machines/xyzab-tdr/tool.tbl",
"G-code: linuxcnc/gcode/ui-session.ngc",
...machineSessionReadinessLogLines(readySessionReadiness),
]);
const restoreLoadCalls = [];
const restoreLoadWorkflow = await restoreMachineSessionSnapshotWorkflow({
machineId: "xyzab-tdr",

View File

@@ -4,7 +4,9 @@ import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
import {
createControlPageBrowserApiManifest,
createControlPageBrowserApiMethods,
createControlPageBrowserApiSchema,
createIniPanelLaunchApiManifest,
createIniPanelShellControlPageContract,
createIniPanelShellViewModel,
@@ -14,6 +16,7 @@ import {
readMachineSessionReadonlyStatus,
refreshMachineSessionControlPage,
renderMachineSessionControlView,
isSupportedIniPanelControlPageApiManifest,
} from "../../../runtime/ui/ini-panel/ui-shell.js";
const __dirname = dirname(fileURLToPath(import.meta.url));
@@ -31,6 +34,20 @@ const expectedMethodNames = [
const contract = createIniPanelShellControlPageContract();
const manifest = createMachineSessionReadonlyStatusApiManifest();
const browserApiSchema = createControlPageBrowserApiSchema();
const browserApiManifest = createControlPageBrowserApiManifest();
assert.deepEqual(browserApiSchema, {
apiName: "ini-panel-control-page",
manifestVersion: 1,
fields: [
"apiName",
"manifestVersion",
"methods",
"contract",
"readonlyStatusApiManifest",
],
});
assert.deepEqual(contract, {
methodNames: expectedMethodNames,
@@ -42,6 +59,7 @@ assert.deepEqual(contract, {
"overview",
"controlView",
"report",
"sessionReadiness",
"runReadiness",
"run",
"session",
@@ -55,22 +73,43 @@ assert.deepEqual(contract, {
],
});
assert.deepEqual(browserApiManifest, {
apiName: "ini-panel-control-page",
manifestVersion: 1,
methods: createControlPageBrowserApiMethods(),
contract,
readonlyStatusApiManifest: manifest,
});
assert.equal(isSupportedIniPanelControlPageApiManifest(browserApiManifest), true);
assert.equal(isSupportedIniPanelControlPageApiManifest({ ...browserApiManifest, manifestVersion: 2 }), false);
assert.equal(isSupportedIniPanelControlPageApiManifest({ ...browserApiManifest, apiName: "other" }), false);
const missingReadonlyManifest = { ...browserApiManifest };
delete missingReadonlyManifest.readonlyStatusApiManifest;
assert.equal(isSupportedIniPanelControlPageApiManifest(missingReadonlyManifest), false);
assert.equal(isSupportedIniPanelControlPageApiManifest(null), false);
const shellViewModel = createIniPanelShellViewModel();
assert.deepEqual(shellViewModel.launchApiManifest, createIniPanelLaunchApiManifest());
assert.deepEqual(Object.keys(shellViewModel.controlPage), [
"browserApiSchema",
"browserApiManifest",
"browserApiMethods",
"contract",
"createController",
"isSupportedBrowserApiManifest",
"loadSessionState",
"readonlyStatusApiManifest",
"readStatus",
"refresh",
"renderView",
]);
assert.deepEqual(shellViewModel.controlPage.browserApiSchema, browserApiSchema);
assert.deepEqual(shellViewModel.controlPage.browserApiManifest, browserApiManifest);
assert.deepEqual(shellViewModel.controlPage.browserApiMethods, createControlPageBrowserApiMethods());
assert.deepEqual(shellViewModel.controlPage.contract, contract);
assert.deepEqual(shellViewModel.controlPage.readonlyStatusApiManifest, manifest);
assert.equal(shellViewModel.controlPage.createController, createMachineSessionControlPageController);
assert.equal(shellViewModel.controlPage.isSupportedBrowserApiManifest, isSupportedIniPanelControlPageApiManifest);
assert.equal(shellViewModel.controlPage.loadSessionState, loadMachineSessionForControlPageWorkflow);
assert.equal(shellViewModel.controlPage.readStatus, readMachineSessionReadonlyStatus);
assert.equal(shellViewModel.controlPage.refresh, refreshMachineSessionControlPage);
@@ -86,8 +125,14 @@ for (const fieldName of contract.readonlyStatusApiStatusFields) {
}
assert.match(shellText, /\bexport function createIniPanelShellControlPageContract\b/);
assert.match(shellText, /\bexport function createControlPageBrowserApiManifest\b/);
assert.match(shellText, /\bexport function createControlPageBrowserApiMethods\b/);
assert.match(shellText, /\bexport function createControlPageBrowserApiSchema\b/);
assert.match(shellText, /\bexport function isSupportedIniPanelControlPageApiManifest\b/);
assert.match(docText, /\bcreateIniPanelShellControlPageContract\b/);
assert.match(docText, /\bcreateControlPageBrowserApiManifest\b/);
assert.match(docText, /\bcreateControlPageBrowserApiSchema\b/);
assert.match(docText, /\bisSupportedIniPanelControlPageApiManifest\b/);
assert.match(docText, /\bcontrolPage\.contract\b/);
assert.doesNotMatch(shellText, /\bparseGcode\b|\bcanonicalEvents\b|\btoolTable\b|\bplanner\b/);

File diff suppressed because it is too large Load Diff

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_shell_integration_manifest.mjs"

View File

@@ -3,6 +3,9 @@ import assert from "node:assert/strict";
import {
createIniPanelStateSummary,
createMachineSessionControlView,
createMachineSessionPersistenceDisplayViewModel,
createMachineSessionPersistenceRenderState,
createMachineSessionPersistenceSummary,
createMachineSessionStateSnapshot,
createMachineSessionStateBundle,
createMachineSessionStateReport,
@@ -68,6 +71,12 @@ const expectedEmptyRunReadinessReport = {
iniWasmPath: null,
};
const sessionReadinessFixture = {
phase: "ready",
ready: true,
missing: [],
};
const expectedWorkflowOverview = {
phase: "ran",
missing: [],
@@ -120,6 +129,7 @@ const summary = createIniPanelStateSummary({
snapshotLabel: "ui-machine-session/ui-machine-session.json",
workflow: "save-session-snapshot",
},
sessionReadiness: sessionReadinessFixture,
sessionLoad: {
iniOpfsPath: "linuxcnc/machines/xyzab-tdr/machine.ini",
iniWasmPath: "/work/session-machine.ini",
@@ -152,6 +162,7 @@ assert.deepEqual(summary, {
snapshotLabel: "ui-machine-session/ui-machine-session.json",
workflow: "save-session-snapshot",
},
sessionReadiness: sessionReadinessFixture,
sessionLoad: {
iniOpfsPath: "linuxcnc/machines/xyzab-tdr/machine.ini",
iniWasmPath: "/work/session-machine.ini",
@@ -175,6 +186,7 @@ assert.deepEqual(
machine: null,
machineFiles: null,
sessionSnapshot: null,
sessionReadiness: null,
sessionLoad: null,
runReadiness: null,
run: null,
@@ -195,6 +207,7 @@ assert.deepEqual(
sessionSnapshot: {
snapshotLabel: "ui-machine-session/ui-machine-session.json",
},
sessionReadiness: sessionReadinessFixture,
sessionLoad: {
iniWasmPath: "/work/session-machine.ini",
},
@@ -237,6 +250,7 @@ assert.deepEqual(
sessionSnapshot: {
snapshotLabel: "ui-machine-session/ui-machine-session.json",
},
sessionReadiness: sessionReadinessFixture,
sessionLoad: {
iniWasmPath: "/work/session-machine.ini",
},
@@ -311,6 +325,7 @@ assert.deepEqual(
parameterWasmPath: "/work/session-linuxcnc.var",
toolTableWasmPath: "/work/session-tool.tbl",
},
sessionReadiness: sessionReadinessFixture,
runReadiness: runReadinessFixture,
selectedProgram: {
source: "snapshot",
@@ -344,6 +359,7 @@ assert.deepEqual(
toolTable: "/work/session-tool.tbl",
gcode: "linuxcnc/gcode/ui-session.ngc",
},
sessionReadiness: sessionReadinessFixture,
loaded: {
iniWasmPath: "/work/session-machine.ini",
parameterWasmPath: "/work/session-linuxcnc.var",
@@ -383,6 +399,7 @@ const expectedExportReport = {
toolTable: null,
gcode: null,
},
sessionReadiness: null,
loaded: {
iniWasmPath: "/work/session-machine.ini",
parameterWasmPath: null,
@@ -446,6 +463,7 @@ assert.deepEqual(
runWorkflow: "save-session-snapshot",
},
runReadiness: runReadinessFixture,
sessionReadiness: sessionReadinessFixture,
run: {
runStatus: "ok",
canonicalEventCount: 2,
@@ -492,6 +510,7 @@ assert.deepEqual(
},
},
runReadiness: expectedRunReadinessReport,
sessionReadiness: sessionReadinessFixture,
lastAction: "run-gcode",
error: null,
session: {
@@ -525,6 +544,7 @@ const expectedBundleReport = {
toolTable: null,
gcode: "linuxcnc/gcode/ui-session.ngc",
},
sessionReadiness: sessionReadinessFixture,
loaded: {
iniWasmPath: null,
parameterWasmPath: null,
@@ -549,6 +569,145 @@ const expectedBundleReport = {
},
};
const readyPersistenceSummary = createMachineSessionPersistenceSummary({
state: {
badges: {
ini: "INI WASM: ready",
interp: "Interpreter WASM: ready",
opfs: "OPFS: ready",
},
machineFiles: {
iniOpfsPath: "linuxcnc/machines/xyzab-tdr/machine.ini",
parameterOpfsPath: "linuxcnc/machines/xyzab-tdr/linuxcnc.var",
toolTableOpfsPath: "linuxcnc/machines/xyzab-tdr/tool.tbl",
gcodeOpfsPath: "linuxcnc/gcode/ui-session.ngc",
},
fields: {
sessionSnapshot: "ui-machine-session/ui-machine-session.json",
sessionIni: "/work/session-machine.ini",
sessionParameters: "/work/session-linuxcnc.var",
sessionToolTable: "/work/session-tool.tbl",
sessionGcode: "linuxcnc/gcode/ui-session.ngc",
},
sessionLoad: {
iniWasmPath: "/work/session-machine.ini",
parameterWasmPath: "/work/session-linuxcnc.var",
toolTableWasmPath: "/work/session-tool.tbl",
},
sessionReadiness: sessionReadinessFixture,
},
readonlyStatus: {
phase: "ready",
ready: true,
},
handoffWorkflowSummary: {
phase: "ready",
ready: true,
},
});
assert.deepEqual(
readyPersistenceSummary,
{
apiName: "machine-session-persistence-summary",
summaryVersion: 1,
phase: "ready",
ready: true,
checks: {
opfs: true,
machineFiles: true,
sessionSnapshot: true,
sessionReadiness: true,
sessionLoad: true,
},
missing: [],
counts: {
machineFiles: 4,
missing: 0,
},
rows: [
{
id: "opfs",
label: "OPFS",
value: "OPFS: ready",
},
{
id: "machine-files",
label: "Machine files",
value: "ini, parameters, tool-table, g-code",
},
{
id: "session-snapshot",
label: "Session snapshot",
value: "ui-machine-session/ui-machine-session.json",
},
{
id: "session-readiness",
label: "Session readiness",
value: "ready",
},
{
id: "session-load",
label: "Session load",
value: "loaded: /work/session-machine.ini",
},
{
id: "readonly-status",
label: "Readonly status",
value: "ready",
},
{
id: "handoff-readiness",
label: "Handoff readiness",
value: "ready",
},
],
},
);
assert.deepEqual(
createMachineSessionPersistenceSummary().missing,
["opfs", "machineFiles", "sessionSnapshot", "sessionReadiness", "sessionLoad"],
);
assert.deepEqual(
createMachineSessionPersistenceDisplayViewModel(readyPersistenceSummary),
{
phase: "ready",
title: "OPFS/session persistence",
statusText: "Ready",
detailText: "No missing persistence requirements",
rows: readyPersistenceSummary.rows,
},
);
assert.deepEqual(
createMachineSessionPersistenceRenderState(
createMachineSessionPersistenceDisplayViewModel(readyPersistenceSummary),
),
{
apiName: "machine-session-persistence-render-state",
renderStateVersion: 1,
phase: "ready",
ready: true,
title: "OPFS/session persistence",
statusText: "Ready",
detailText: "No missing persistence requirements",
statusLine: "Ready: No missing persistence requirements",
rows: readyPersistenceSummary.rows,
dataset: {
persistencePhase: "ready",
persistenceReady: "true",
persistenceScope: "opfs-session",
},
},
);
assert.equal(
createMachineSessionPersistenceRenderState().statusLine,
"Waiting: opfs, machineFiles, sessionSnapshot, sessionReadiness, sessionLoad",
);
assert.deepEqual(
createMachineSessionWorkflowOverview(expectedBundleReport),
expectedWorkflowOverview,
@@ -569,6 +728,7 @@ assert.deepEqual(
runWorkflow: "save-session-snapshot",
},
runReadiness: runReadinessFixture,
sessionReadiness: sessionReadinessFixture,
run: {
runStatus: "ok",
canonicalEventCount: 2,
@@ -593,6 +753,7 @@ assert.deepEqual(
opfs: "OPFS: ready",
},
overview: expectedWorkflowOverview,
sessionReadiness: sessionReadinessFixture,
lastAction: "run-gcode",
error: null,
runReadiness: expectedRunReadinessReport,

File diff suppressed because it is too large Load Diff

View File

@@ -15,6 +15,7 @@ ROOT_DIR="$(cd "$(dirname "$0")/../../.." && pwd)"
"$ROOT_DIR/tests/ui/node/verify_ini_panel_readonly_status_api.sh"
"$ROOT_DIR/tests/ui/node/verify_ini_panel_entry_manifest.sh"
"$ROOT_DIR/tests/ui/node/verify_ini_panel_launch_api_manifest.sh"
"$ROOT_DIR/tests/ui/node/verify_ini_panel_shell_integration_manifest.sh"
"$ROOT_DIR/tests/ui/node/verify_ini_panel_ui_shell.sh"
"$ROOT_DIR/tests/ui/node/verify_ini_panel_shell_control_page_contract.sh"
"$ROOT_DIR/tests/ui/node/verify_ini_panel_entry_docs.sh"