上传真实浏览器仿真推进
结论:新增 LinuxCNC WASM 驱动的真实浏览器仿真页面,接入 browser smoke、release gate、SDK 与项目交付文档,并完成当前推进批次的验收记录。
This commit is contained in:
@@ -44,8 +44,27 @@ import {
|
||||
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,
|
||||
@@ -87,14 +106,35 @@ import {
|
||||
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,
|
||||
@@ -227,6 +267,35 @@ 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
|
||||
@@ -273,6 +342,13 @@ 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
|
||||
|
||||
@@ -339,12 +415,57 @@ for external browser shells:
|
||||
- `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` for dashboards.
|
||||
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()`,
|
||||
|
||||
@@ -1,6 +1,18 @@
|
||||
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,
|
||||
@@ -14,6 +26,7 @@ export {
|
||||
createProjectReleaseReadinessArtifactValidationSummaryViewModel,
|
||||
createProjectReleaseReadinessReport,
|
||||
createProjectReleaseReadinessSummaryViewModel,
|
||||
loadProjectBatchAcceptanceReportUrlWorkflow,
|
||||
loadProjectReleaseReadinessArtifactUrlWorkflow,
|
||||
parseProjectReleaseReadinessArtifactJson,
|
||||
} from "./project-release-readiness.js";
|
||||
@@ -109,8 +122,27 @@ export {
|
||||
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,
|
||||
@@ -125,6 +157,14 @@ export {
|
||||
isSupportedIniPanelShellSessionHandoffPackage,
|
||||
isSupportedIniPanelShellSessionHandoffSummary,
|
||||
loadIniPanelShellWorkflowOverviewReleaseReadinessArtifactUrl,
|
||||
runIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflow,
|
||||
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflowSummaryViewModel,
|
||||
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflowActionPlan,
|
||||
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflowRenderState,
|
||||
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflowDomContract,
|
||||
createIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflowDomReadiness,
|
||||
renderIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflowState,
|
||||
mountIniPanelShellWorkflowOverviewReleaseReadinessArtifactJsonWorkflowState,
|
||||
mountIniPanelShellSessionHandoff,
|
||||
mountIniPanelShellWorkflowOverviewEmbedding,
|
||||
mountIniPanelShellWorkflowOverviewEmbeddingMountState,
|
||||
|
||||
@@ -46,6 +46,12 @@ const REQUIRED_RELEASE_GATES = [
|
||||
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",
|
||||
@@ -336,6 +342,864 @@ export function createProjectReleaseGateActionPlan({
|
||||
};
|
||||
}
|
||||
|
||||
const BATCH_ACCEPTANCE_CAPABILITY_TYPES = [
|
||||
"api",
|
||||
"workflow",
|
||||
"gate",
|
||||
"browser",
|
||||
];
|
||||
|
||||
function normalizeBatchCapabilities(capabilities) {
|
||||
return arrayOrEmpty(capabilities)
|
||||
.map((capability) => objectOrEmpty(capability))
|
||||
.filter(({ id, type }) => typeof id === "string" && typeof type === "string")
|
||||
.map((capability) => ({
|
||||
id: capability.id,
|
||||
type: capability.type,
|
||||
label: capability.label ?? capability.id,
|
||||
evidence: capability.evidence ?? null,
|
||||
command: capability.command ?? null,
|
||||
}));
|
||||
}
|
||||
|
||||
export function createProjectBatchAcceptanceCapabilityMatrix({
|
||||
capabilities = [],
|
||||
} = {}) {
|
||||
const normalizedCapabilities = normalizeBatchCapabilities(capabilities);
|
||||
const acceptedCapabilities = normalizedCapabilities.filter(({ type }) =>
|
||||
BATCH_ACCEPTANCE_CAPABILITY_TYPES.includes(type)
|
||||
);
|
||||
const rejectedCapabilities = normalizedCapabilities.filter(({ type }) =>
|
||||
!BATCH_ACCEPTANCE_CAPABILITY_TYPES.includes(type)
|
||||
);
|
||||
const capabilityTypes = [...new Set(acceptedCapabilities.map(({ type }) => type))];
|
||||
const countsByType = BATCH_ACCEPTANCE_CAPABILITY_TYPES.reduce((counts, type) => ({
|
||||
...counts,
|
||||
[type]: acceptedCapabilities.filter((capability) => capability.type === type).length,
|
||||
}), {});
|
||||
const missing = acceptedCapabilities.length > 0 ? [] : ["batch-capability"];
|
||||
|
||||
return {
|
||||
apiName: "project-batch-acceptance-capability-matrix",
|
||||
matrixVersion: 1,
|
||||
phase: missing.length === 0 ? "ready" : "blocked",
|
||||
ready: missing.length === 0,
|
||||
acceptedCapabilityTypes: BATCH_ACCEPTANCE_CAPABILITY_TYPES,
|
||||
capabilityTypes,
|
||||
capabilityCount: acceptedCapabilities.length,
|
||||
acceptedCount: acceptedCapabilities.length,
|
||||
rejectedCount: rejectedCapabilities.length,
|
||||
countsByType,
|
||||
capabilities: acceptedCapabilities,
|
||||
rejectedCapabilities,
|
||||
missing,
|
||||
rows: [
|
||||
{
|
||||
id: "accepted-capabilities",
|
||||
label: "Accepted capabilities",
|
||||
value: `${acceptedCapabilities.length}`,
|
||||
},
|
||||
{
|
||||
id: "accepted-types",
|
||||
label: "Accepted capability types",
|
||||
value: capabilityTypes.join(", ") || "none",
|
||||
},
|
||||
{
|
||||
id: "rejected-capabilities",
|
||||
label: "Rejected capabilities",
|
||||
value: `${rejectedCapabilities.length}`,
|
||||
},
|
||||
{
|
||||
id: "missing",
|
||||
label: "Missing capability evidence",
|
||||
value: missing.length > 0 ? missing.join(", ") : "none",
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
export function createProjectBatchAcceptanceWorkflow({
|
||||
batchId = "current-batch",
|
||||
capabilities = [],
|
||||
gateResults = {},
|
||||
observedOutputs = [],
|
||||
} = {}) {
|
||||
const capabilityMatrix = createProjectBatchAcceptanceCapabilityMatrix({ capabilities });
|
||||
const gateManifest = createProjectReleaseGateManifest();
|
||||
const gateResultMatrix = createProjectReleaseGateResultMatrix({
|
||||
gateResults,
|
||||
observedOutputs,
|
||||
manifest: gateManifest,
|
||||
});
|
||||
const batchGatePassed = gateResultMatrix.rows
|
||||
.find(({ id }) => id === "project-batch-acceptance")?.passed === true;
|
||||
const ready = capabilityMatrix.ready && batchGatePassed;
|
||||
const missing = [
|
||||
...capabilityMatrix.missing,
|
||||
...(batchGatePassed ? [] : ["project-batch-acceptance"]),
|
||||
];
|
||||
|
||||
return {
|
||||
apiName: "project-batch-acceptance-workflow",
|
||||
workflowVersion: 1,
|
||||
phase: ready ? "ready" : "blocked",
|
||||
ready,
|
||||
batchId,
|
||||
acceptedCapabilityTypes: capabilityMatrix.acceptedCapabilityTypes,
|
||||
capabilityTypes: capabilityMatrix.capabilityTypes,
|
||||
capabilityCount: capabilityMatrix.capabilityCount,
|
||||
capabilityMatrix,
|
||||
capabilities: capabilityMatrix.capabilities,
|
||||
rejectedCapabilities: capabilityMatrix.rejectedCapabilities,
|
||||
gateResultMatrix,
|
||||
missing,
|
||||
rows: [
|
||||
{
|
||||
id: "batch",
|
||||
label: "Batch",
|
||||
value: batchId,
|
||||
},
|
||||
{
|
||||
id: "capabilities",
|
||||
label: "Accepted capabilities",
|
||||
value: capabilityMatrix.capabilityTypes.join(", ") || "none",
|
||||
},
|
||||
{
|
||||
id: "project-batch-acceptance",
|
||||
label: "Batch acceptance gate",
|
||||
value: batchGatePassed ? "passed" : "unknown",
|
||||
},
|
||||
{
|
||||
id: "missing",
|
||||
label: "Missing acceptance evidence",
|
||||
value: missing.length > 0 ? missing.join(", ") : "none",
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
export function createProjectBatchAcceptanceSummaryViewModel(
|
||||
workflow = createProjectBatchAcceptanceWorkflow(),
|
||||
) {
|
||||
const missing = arrayOrEmpty(workflow?.missing);
|
||||
const ready = workflow?.ready === true;
|
||||
const statusText = ready ? "Ready" : "Blocked";
|
||||
const detailText = ready
|
||||
? "Batch acceptance evidence complete"
|
||||
: (missing.length > 0 ? missing.join(", ") : "batch acceptance evidence missing");
|
||||
return {
|
||||
apiName: "project-batch-acceptance-summary-view-model",
|
||||
viewModelVersion: 1,
|
||||
phase: ready ? "ready" : "blocked",
|
||||
ready,
|
||||
title: "Project batch acceptance",
|
||||
statusText,
|
||||
detailText,
|
||||
statusLine: `${statusText}: ${detailText}`,
|
||||
rows: [
|
||||
{
|
||||
id: "batch",
|
||||
label: "Batch",
|
||||
value: workflow?.batchId ?? "current-batch",
|
||||
},
|
||||
{
|
||||
id: "capability-count",
|
||||
label: "Accepted capability count",
|
||||
value: `${workflow?.capabilityCount ?? 0}`,
|
||||
},
|
||||
{
|
||||
id: "capability-types",
|
||||
label: "Accepted capability types",
|
||||
value: arrayOrEmpty(workflow?.capabilityTypes).join(", ") || "none",
|
||||
},
|
||||
{
|
||||
id: "missing",
|
||||
label: "Missing acceptance evidence",
|
||||
value: missing.length > 0 ? missing.join(", ") : "none",
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
export function createProjectBatchAcceptanceActionPlan(
|
||||
workflow = createProjectBatchAcceptanceWorkflow(),
|
||||
) {
|
||||
const missing = arrayOrEmpty(workflow?.missing);
|
||||
const ready = workflow?.ready === true;
|
||||
const commands = ready ? [] : [
|
||||
...(missing.includes("project-batch-acceptance")
|
||||
? [{
|
||||
step: 1,
|
||||
id: "project-batch-acceptance",
|
||||
label: "Run 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",
|
||||
}]
|
||||
: []),
|
||||
];
|
||||
const shellScript = commands.length > 0
|
||||
? [
|
||||
"#!/usr/bin/env bash",
|
||||
"set -euo pipefail",
|
||||
"",
|
||||
...commands.map(({ command }) => command),
|
||||
"",
|
||||
].join("\n")
|
||||
: (ready
|
||||
? "# Project batch acceptance workflow is already ready.\n"
|
||||
: "# Project batch acceptance workflow requires at least one API, workflow, gate, or browser capability.\n");
|
||||
|
||||
return {
|
||||
apiName: "project-batch-acceptance-action-plan",
|
||||
planVersion: 1,
|
||||
phase: ready ? "ready" : "blocked",
|
||||
ready,
|
||||
missing,
|
||||
commandCount: commands.length,
|
||||
nextActionId: commands[0]?.id ?? (missing[0] ?? null),
|
||||
nextCommand: commands[0]?.command ?? null,
|
||||
commands,
|
||||
shellScript,
|
||||
rows: [
|
||||
{
|
||||
id: "batch-acceptance",
|
||||
label: "Batch acceptance",
|
||||
value: ready ? "ready" : "blocked",
|
||||
},
|
||||
{
|
||||
id: "missing",
|
||||
label: "Missing acceptance evidence",
|
||||
value: missing.length > 0 ? missing.join(", ") : "none",
|
||||
},
|
||||
{
|
||||
id: "next-command",
|
||||
label: "Next command",
|
||||
value: commands[0]?.command ?? "none",
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
export function createProjectBatchAcceptanceChecklist({
|
||||
workflow = createProjectBatchAcceptanceWorkflow(),
|
||||
actionPlan = createProjectBatchAcceptanceActionPlan(workflow),
|
||||
} = {}) {
|
||||
const capabilityMatrix = objectOrEmpty(workflow?.capabilityMatrix);
|
||||
const ready = workflow?.ready === true;
|
||||
const items = [
|
||||
{
|
||||
id: "accepted-capability",
|
||||
label: "At least one API, workflow, gate, or browser capability",
|
||||
status: capabilityMatrix.ready === true ? "pass" : "blocked",
|
||||
passed: capabilityMatrix.ready === true,
|
||||
detail: arrayOrEmpty(workflow?.capabilityTypes).join(", ") || "none",
|
||||
},
|
||||
{
|
||||
id: "batch-acceptance-gate",
|
||||
label: "Batch acceptance gate evidence",
|
||||
status: !arrayOrEmpty(workflow?.missing).includes("project-batch-acceptance") ? "pass" : "blocked",
|
||||
passed: !arrayOrEmpty(workflow?.missing).includes("project-batch-acceptance"),
|
||||
detail: workflow?.gateResultMatrix?.rows
|
||||
?.find?.(({ id }) => id === "project-batch-acceptance")?.status ?? "unknown",
|
||||
},
|
||||
{
|
||||
id: "next-action",
|
||||
label: "Next acceptance action",
|
||||
status: actionPlan?.nextActionId ? "blocked" : "pass",
|
||||
passed: !actionPlan?.nextActionId,
|
||||
detail: actionPlan?.nextCommand ?? "none",
|
||||
},
|
||||
];
|
||||
|
||||
return {
|
||||
apiName: "project-batch-acceptance-checklist",
|
||||
checklistVersion: 1,
|
||||
phase: ready ? "ready" : "blocked",
|
||||
ready,
|
||||
batchId: workflow?.batchId ?? "current-batch",
|
||||
passedCount: items.filter(({ passed }) => passed).length,
|
||||
blockedCount: items.filter(({ passed }) => !passed).length,
|
||||
items,
|
||||
rows: items.map(({ id, label, status, detail }) => ({
|
||||
id,
|
||||
label,
|
||||
value: `${status}: ${detail}`,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
export function createProjectBatchAcceptanceReport({
|
||||
batchId = "current-batch",
|
||||
capabilities = [],
|
||||
gateResults = {},
|
||||
observedOutputs = [],
|
||||
} = {}) {
|
||||
const capabilityMatrix = createProjectBatchAcceptanceCapabilityMatrix({ capabilities });
|
||||
const workflow = createProjectBatchAcceptanceWorkflow({
|
||||
batchId,
|
||||
capabilities,
|
||||
gateResults,
|
||||
observedOutputs,
|
||||
});
|
||||
const summaryViewModel = createProjectBatchAcceptanceSummaryViewModel(workflow);
|
||||
const actionPlan = createProjectBatchAcceptanceActionPlan(workflow);
|
||||
const checklist = createProjectBatchAcceptanceChecklist({ workflow, actionPlan });
|
||||
|
||||
return {
|
||||
apiName: "project-batch-acceptance-report",
|
||||
reportVersion: 1,
|
||||
phase: workflow.phase,
|
||||
ready: workflow.ready,
|
||||
batchId: workflow.batchId,
|
||||
missing: [...workflow.missing],
|
||||
capabilityMatrix,
|
||||
workflow,
|
||||
summaryViewModel,
|
||||
actionPlan,
|
||||
checklist,
|
||||
rows: [
|
||||
{
|
||||
id: "summary",
|
||||
label: "Batch acceptance summary",
|
||||
value: summaryViewModel.statusLine,
|
||||
},
|
||||
{
|
||||
id: "capabilities",
|
||||
label: "Accepted capabilities",
|
||||
value: capabilityMatrix.capabilityTypes.join(", ") || "none",
|
||||
},
|
||||
{
|
||||
id: "checklist",
|
||||
label: "Checklist",
|
||||
value: `${checklist.passedCount}/${checklist.items.length} passed`,
|
||||
},
|
||||
{
|
||||
id: "next-command",
|
||||
label: "Next command",
|
||||
value: actionPlan.nextCommand ?? "none",
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
export function createProjectBatchAcceptanceReportValidation(report = {}) {
|
||||
const reportObject = objectOrEmpty(report);
|
||||
const capabilityMatrix = objectOrEmpty(reportObject.capabilityMatrix);
|
||||
const workflow = objectOrEmpty(reportObject.workflow);
|
||||
const summaryViewModel = objectOrEmpty(reportObject.summaryViewModel);
|
||||
const actionPlan = objectOrEmpty(reportObject.actionPlan);
|
||||
const checklist = objectOrEmpty(reportObject.checklist);
|
||||
const rows = arrayOrEmpty(reportObject.rows);
|
||||
const missing = [
|
||||
...(reportObject.apiName === "project-batch-acceptance-report" ? [] : ["apiName"]),
|
||||
...(reportObject.reportVersion === 1 ? [] : ["reportVersion"]),
|
||||
...(reportObject.phase === "ready" ? [] : ["phase"]),
|
||||
...(reportObject.ready === true ? [] : ["ready"]),
|
||||
...(Array.isArray(reportObject.missing) && reportObject.missing.length === 0 ? [] : ["missing"]),
|
||||
...(capabilityMatrix.apiName === "project-batch-acceptance-capability-matrix"
|
||||
&& capabilityMatrix.ready === true
|
||||
? []
|
||||
: ["capabilityMatrix"]),
|
||||
...(workflow.apiName === "project-batch-acceptance-workflow" && workflow.ready === true
|
||||
? []
|
||||
: ["workflow"]),
|
||||
...(summaryViewModel.apiName === "project-batch-acceptance-summary-view-model"
|
||||
&& summaryViewModel.ready === true
|
||||
? []
|
||||
: ["summaryViewModel"]),
|
||||
...(actionPlan.apiName === "project-batch-acceptance-action-plan" && actionPlan.ready === true
|
||||
? []
|
||||
: ["actionPlan"]),
|
||||
...(checklist.apiName === "project-batch-acceptance-checklist" && checklist.ready === true
|
||||
? []
|
||||
: ["checklist"]),
|
||||
...(rows.find(({ id, value }) => id === "checklist" && value === "3/3 passed")
|
||||
? []
|
||||
: ["rows.checklist"]),
|
||||
];
|
||||
|
||||
return {
|
||||
apiName: "project-batch-acceptance-report-validation",
|
||||
validationVersion: 1,
|
||||
phase: missing.length === 0 ? "ready" : "blocked",
|
||||
ready: missing.length === 0,
|
||||
missing,
|
||||
batchId: reportObject.batchId ?? null,
|
||||
reportApiName: reportObject.apiName ?? null,
|
||||
reportVersion: reportObject.reportVersion ?? null,
|
||||
rows: [
|
||||
{
|
||||
id: "report",
|
||||
label: "Batch acceptance report",
|
||||
value: reportObject.apiName ?? "missing",
|
||||
},
|
||||
{
|
||||
id: "capability-matrix",
|
||||
label: "Capability matrix",
|
||||
value: capabilityMatrix.ready === true ? "ready" : "missing",
|
||||
},
|
||||
{
|
||||
id: "workflow",
|
||||
label: "Batch workflow",
|
||||
value: workflow.ready === true ? "ready" : "missing",
|
||||
},
|
||||
{
|
||||
id: "checklist",
|
||||
label: "Checklist",
|
||||
value: checklist.ready === true ? "ready" : "missing",
|
||||
},
|
||||
{
|
||||
id: "missing",
|
||||
label: "Missing report evidence",
|
||||
value: missing.length > 0 ? missing.join(", ") : "none",
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
export function createProjectBatchAcceptanceReportValidationSummaryViewModel(
|
||||
validation = createProjectBatchAcceptanceReportValidation(),
|
||||
) {
|
||||
const missing = arrayOrEmpty(validation?.missing);
|
||||
const ready = validation?.ready === true;
|
||||
const statusText = ready ? "Ready" : "Blocked";
|
||||
const detailText = ready
|
||||
? "Batch acceptance report evidence complete"
|
||||
: (missing.length > 0 ? missing.join(", ") : "batch acceptance report evidence missing");
|
||||
return {
|
||||
apiName: "project-batch-acceptance-report-validation-summary-view-model",
|
||||
viewModelVersion: 1,
|
||||
phase: ready ? "ready" : "blocked",
|
||||
ready,
|
||||
title: "Project batch acceptance report validation",
|
||||
statusText,
|
||||
detailText,
|
||||
statusLine: `${statusText}: ${detailText}`,
|
||||
rows: [
|
||||
{
|
||||
id: "report",
|
||||
label: "Batch acceptance report",
|
||||
value: validation?.reportApiName ?? "missing",
|
||||
},
|
||||
{
|
||||
id: "batch",
|
||||
label: "Batch",
|
||||
value: validation?.batchId ?? "missing",
|
||||
},
|
||||
{
|
||||
id: "validation",
|
||||
label: "Report validation",
|
||||
value: ready ? "ready" : "blocked",
|
||||
},
|
||||
{
|
||||
id: "missing",
|
||||
label: "Missing report evidence",
|
||||
value: missing.length > 0 ? missing.join(", ") : "none",
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
export function createProjectBatchAcceptanceReportValidationActionPlan(
|
||||
validation = createProjectBatchAcceptanceReportValidation(),
|
||||
) {
|
||||
const missing = arrayOrEmpty(validation?.missing);
|
||||
const ready = validation?.ready === true;
|
||||
const commands = ready ? [] : [
|
||||
{
|
||||
step: 1,
|
||||
id: "project-batch-acceptance",
|
||||
label: "Run project batch acceptance workflow and artifact gate",
|
||||
command: "wasm-port/tests/sdk/node/verify_project_batch_acceptance_workflow.sh",
|
||||
expectedOutput: "project_batch_acceptance_artifact_node_smoke=ok",
|
||||
},
|
||||
];
|
||||
const shellScript = commands.length > 0
|
||||
? [
|
||||
"#!/usr/bin/env bash",
|
||||
"set -euo pipefail",
|
||||
"",
|
||||
...commands.map(({ command }) => command),
|
||||
"",
|
||||
].join("\n")
|
||||
: "# Project batch acceptance report validation is already ready.\n";
|
||||
|
||||
return {
|
||||
apiName: "project-batch-acceptance-report-validation-action-plan",
|
||||
planVersion: 1,
|
||||
phase: ready ? "ready" : "blocked",
|
||||
ready,
|
||||
missing,
|
||||
missingCount: missing.length,
|
||||
commandCount: commands.length,
|
||||
nextActionId: commands[0]?.id ?? null,
|
||||
nextCommand: commands[0]?.command ?? null,
|
||||
commands,
|
||||
shellScript,
|
||||
rows: [
|
||||
{
|
||||
id: "report-validation",
|
||||
label: "Report validation",
|
||||
value: ready ? "ready" : "blocked",
|
||||
},
|
||||
{
|
||||
id: "missing",
|
||||
label: "Missing report evidence",
|
||||
value: missing.length > 0 ? missing.join(", ") : "none",
|
||||
},
|
||||
{
|
||||
id: "next-command",
|
||||
label: "Next command",
|
||||
value: commands[0]?.command ?? "none",
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
export function createProjectBatchAcceptanceReportJsonWorkflow({ reportJson = "" } = {}) {
|
||||
try {
|
||||
const report = JSON.parse(String(reportJson));
|
||||
const validation = createProjectBatchAcceptanceReportValidation(report);
|
||||
return {
|
||||
apiName: "project-batch-acceptance-report-json-workflow",
|
||||
workflowVersion: 1,
|
||||
phase: validation.ready ? "ready" : "blocked",
|
||||
ready: validation.ready,
|
||||
parsed: true,
|
||||
parseError: null,
|
||||
report,
|
||||
validation,
|
||||
missing: validation.missing,
|
||||
rows: [
|
||||
{
|
||||
id: "parse",
|
||||
label: "Batch report JSON parse",
|
||||
value: "ready",
|
||||
},
|
||||
{
|
||||
id: "validation",
|
||||
label: "Batch report validation",
|
||||
value: validation.ready ? "ready" : "blocked",
|
||||
},
|
||||
{
|
||||
id: "missing",
|
||||
label: "Missing report evidence",
|
||||
value: validation.missing.length > 0 ? validation.missing.join(", ") : "none",
|
||||
},
|
||||
],
|
||||
};
|
||||
} catch (error) {
|
||||
const validation = createProjectBatchAcceptanceReportValidation({});
|
||||
return {
|
||||
apiName: "project-batch-acceptance-report-json-workflow",
|
||||
workflowVersion: 1,
|
||||
phase: "blocked",
|
||||
ready: false,
|
||||
parsed: false,
|
||||
parseError: error?.message ?? "batch acceptance report JSON parse error",
|
||||
report: null,
|
||||
validation,
|
||||
missing: ["report-json", ...validation.missing],
|
||||
rows: [
|
||||
{
|
||||
id: "parse",
|
||||
label: "Batch report JSON parse",
|
||||
value: "blocked",
|
||||
},
|
||||
{
|
||||
id: "validation",
|
||||
label: "Batch report validation",
|
||||
value: "blocked",
|
||||
},
|
||||
{
|
||||
id: "missing",
|
||||
label: "Missing report evidence",
|
||||
value: ["report-json", ...validation.missing].join(", "),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export async function loadProjectBatchAcceptanceReportUrlWorkflow({
|
||||
reportUrl = "",
|
||||
fetchRef = globalThis.fetch,
|
||||
} = {}) {
|
||||
if (!reportUrl || typeof fetchRef !== "function") {
|
||||
const jsonWorkflow = createProjectBatchAcceptanceReportJsonWorkflow({ reportJson: "" });
|
||||
const missing = [
|
||||
...(reportUrl ? [] : ["report-url"]),
|
||||
...(typeof fetchRef === "function" ? [] : ["fetch"]),
|
||||
...jsonWorkflow.missing,
|
||||
];
|
||||
return {
|
||||
apiName: "project-batch-acceptance-report-url-workflow",
|
||||
workflowVersion: 1,
|
||||
phase: "blocked",
|
||||
ready: false,
|
||||
reportUrl,
|
||||
fetched: false,
|
||||
httpStatus: null,
|
||||
fetchError: null,
|
||||
jsonWorkflow,
|
||||
validation: jsonWorkflow.validation,
|
||||
missing,
|
||||
rows: [
|
||||
{
|
||||
id: "fetch",
|
||||
label: "Batch report URL fetch",
|
||||
value: "blocked",
|
||||
},
|
||||
{
|
||||
id: "json-workflow",
|
||||
label: "Batch report JSON workflow",
|
||||
value: "blocked",
|
||||
},
|
||||
{
|
||||
id: "missing",
|
||||
label: "Missing report evidence",
|
||||
value: missing.join(", "),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetchRef(reportUrl);
|
||||
const httpStatus = response?.status ?? null;
|
||||
if (!response?.ok) {
|
||||
const jsonWorkflow = createProjectBatchAcceptanceReportJsonWorkflow({ reportJson: "" });
|
||||
const fetchError = `batch report URL fetch failed${httpStatus ? `: ${httpStatus}` : ""}`;
|
||||
return {
|
||||
apiName: "project-batch-acceptance-report-url-workflow",
|
||||
workflowVersion: 1,
|
||||
phase: "blocked",
|
||||
ready: false,
|
||||
reportUrl,
|
||||
fetched: false,
|
||||
httpStatus,
|
||||
fetchError,
|
||||
jsonWorkflow,
|
||||
validation: jsonWorkflow.validation,
|
||||
missing: ["report-fetch", ...jsonWorkflow.missing],
|
||||
rows: [
|
||||
{
|
||||
id: "fetch",
|
||||
label: "Batch report URL fetch",
|
||||
value: "blocked",
|
||||
},
|
||||
{
|
||||
id: "json-workflow",
|
||||
label: "Batch report JSON workflow",
|
||||
value: "blocked",
|
||||
},
|
||||
{
|
||||
id: "missing",
|
||||
label: "Missing report evidence",
|
||||
value: ["report-fetch", ...jsonWorkflow.missing].join(", "),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
const reportJson = await response.text();
|
||||
const jsonWorkflow = createProjectBatchAcceptanceReportJsonWorkflow({ reportJson });
|
||||
return {
|
||||
apiName: "project-batch-acceptance-report-url-workflow",
|
||||
workflowVersion: 1,
|
||||
phase: jsonWorkflow.ready === true ? "ready" : "blocked",
|
||||
ready: jsonWorkflow.ready === true,
|
||||
reportUrl,
|
||||
fetched: true,
|
||||
httpStatus,
|
||||
fetchError: null,
|
||||
jsonWorkflow,
|
||||
validation: jsonWorkflow.validation,
|
||||
missing: jsonWorkflow.missing,
|
||||
rows: [
|
||||
{
|
||||
id: "fetch",
|
||||
label: "Batch report URL fetch",
|
||||
value: "ready",
|
||||
},
|
||||
{
|
||||
id: "json-workflow",
|
||||
label: "Batch report JSON workflow",
|
||||
value: jsonWorkflow.ready === true ? "ready" : "blocked",
|
||||
},
|
||||
{
|
||||
id: "missing",
|
||||
label: "Missing report evidence",
|
||||
value: jsonWorkflow.missing.length > 0 ? jsonWorkflow.missing.join(", ") : "none",
|
||||
},
|
||||
],
|
||||
};
|
||||
} catch (error) {
|
||||
const jsonWorkflow = createProjectBatchAcceptanceReportJsonWorkflow({ reportJson: "" });
|
||||
return {
|
||||
apiName: "project-batch-acceptance-report-url-workflow",
|
||||
workflowVersion: 1,
|
||||
phase: "blocked",
|
||||
ready: false,
|
||||
reportUrl,
|
||||
fetched: false,
|
||||
httpStatus: null,
|
||||
fetchError: error?.message ?? "batch report URL fetch error",
|
||||
jsonWorkflow,
|
||||
validation: jsonWorkflow.validation,
|
||||
missing: ["report-fetch", ...jsonWorkflow.missing],
|
||||
rows: [
|
||||
{
|
||||
id: "fetch",
|
||||
label: "Batch report URL fetch",
|
||||
value: "blocked",
|
||||
},
|
||||
{
|
||||
id: "json-workflow",
|
||||
label: "Batch report JSON workflow",
|
||||
value: "blocked",
|
||||
},
|
||||
{
|
||||
id: "missing",
|
||||
label: "Missing report evidence",
|
||||
value: ["report-fetch", ...jsonWorkflow.missing].join(", "),
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export function createProjectBatchAcceptanceReportUrlWorkflowSummaryViewModel(
|
||||
workflow = {},
|
||||
) {
|
||||
const missing = arrayOrEmpty(workflow?.missing);
|
||||
const ready = workflow?.ready === true;
|
||||
const statusText = ready ? "Ready" : "Blocked";
|
||||
const detailText = ready
|
||||
? "Batch report URL workflow complete"
|
||||
: (missing.length > 0 ? missing.join(", ") : workflow?.fetchError ?? "batch report URL workflow blocked");
|
||||
return {
|
||||
apiName: "project-batch-acceptance-report-url-workflow-summary-view-model",
|
||||
viewModelVersion: 1,
|
||||
phase: ready ? "ready" : "blocked",
|
||||
ready,
|
||||
title: "Project batch acceptance report URL workflow",
|
||||
statusText,
|
||||
detailText,
|
||||
statusLine: `${statusText}: ${detailText}`,
|
||||
rows: [
|
||||
{
|
||||
id: "report-url",
|
||||
label: "Report URL",
|
||||
value: workflow?.reportUrl || "missing",
|
||||
},
|
||||
{
|
||||
id: "fetch",
|
||||
label: "Fetch",
|
||||
value: workflow?.fetched === true ? `ready${workflow?.httpStatus ? ` (${workflow.httpStatus})` : ""}` : "blocked",
|
||||
},
|
||||
{
|
||||
id: "json-workflow",
|
||||
label: "JSON workflow",
|
||||
value: workflow?.jsonWorkflow?.ready === true ? "ready" : "blocked",
|
||||
},
|
||||
{
|
||||
id: "report-validation",
|
||||
label: "Report validation",
|
||||
value: workflow?.validation?.ready === true ? "ready" : "blocked",
|
||||
},
|
||||
{
|
||||
id: "missing",
|
||||
label: "Missing workflow evidence",
|
||||
value: missing.length > 0 ? missing.join(", ") : "none",
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
export function createProjectBatchAcceptanceReportUrlWorkflowActionPlan(
|
||||
workflow = {},
|
||||
) {
|
||||
const missing = arrayOrEmpty(workflow?.missing);
|
||||
const ready = workflow?.ready === true;
|
||||
const inputActions = [
|
||||
...(missing.includes("report-url")
|
||||
? [{
|
||||
id: "provide-report-url",
|
||||
label: "Provide project batch acceptance report URL",
|
||||
kind: "input",
|
||||
command: null,
|
||||
expectedOutput: null,
|
||||
}]
|
||||
: []),
|
||||
...(missing.includes("fetch")
|
||||
? [{
|
||||
id: "provide-fetch",
|
||||
label: "Provide fetch implementation",
|
||||
kind: "input",
|
||||
command: null,
|
||||
expectedOutput: null,
|
||||
}]
|
||||
: []),
|
||||
...(missing.includes("report-fetch")
|
||||
? [{
|
||||
id: "verify-report-url-fetch",
|
||||
label: "Verify project batch acceptance report URL fetch",
|
||||
kind: "fetch",
|
||||
command: null,
|
||||
expectedOutput: null,
|
||||
}]
|
||||
: []),
|
||||
];
|
||||
const commandActions = ready ? [] : [
|
||||
{
|
||||
id: "project-batch-acceptance",
|
||||
label: "Run project batch acceptance workflow smoke",
|
||||
kind: "command",
|
||||
command: "wasm-port/tests/sdk/node/verify_project_batch_acceptance_workflow.sh",
|
||||
expectedOutput: "project_batch_acceptance_workflow_node_smoke=ok",
|
||||
},
|
||||
];
|
||||
const actions = ready ? [] : [...inputActions, ...commandActions].map((action, index) => ({
|
||||
step: index + 1,
|
||||
...action,
|
||||
}));
|
||||
const commands = actions.filter(({ command }) => typeof command === "string" && command.length > 0);
|
||||
const shellScript = commands.length > 0
|
||||
? [
|
||||
"#!/usr/bin/env bash",
|
||||
"set -euo pipefail",
|
||||
"",
|
||||
...commands.map(({ command }) => command),
|
||||
"",
|
||||
].join("\n")
|
||||
: (ready
|
||||
? "# Project batch acceptance report URL workflow is already ready.\n"
|
||||
: "# Project batch acceptance report URL workflow requires non-shell input.\n");
|
||||
|
||||
return {
|
||||
apiName: "project-batch-acceptance-report-url-workflow-action-plan",
|
||||
planVersion: 1,
|
||||
phase: ready ? "ready" : "blocked",
|
||||
ready,
|
||||
missing,
|
||||
actionCount: actions.length,
|
||||
commandCount: commands.length,
|
||||
nextActionId: actions[0]?.id ?? null,
|
||||
nextCommand: commands[0]?.command ?? null,
|
||||
actions,
|
||||
commands,
|
||||
shellScript,
|
||||
rows: actions.map(({ id, label, kind, command, expectedOutput }) => ({
|
||||
id,
|
||||
label,
|
||||
kind,
|
||||
value: command ?? kind,
|
||||
expectedOutput,
|
||||
})),
|
||||
};
|
||||
}
|
||||
|
||||
export function createProjectReleaseReadinessReport({
|
||||
gateResults = {},
|
||||
observedOutputs = [],
|
||||
|
||||
@@ -91,6 +91,7 @@
|
||||
<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>
|
||||
@@ -297,15 +298,124 @@
|
||||
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)
|
||||
),
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -106,9 +106,36 @@
|
||||
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,
|
||||
@@ -146,15 +173,124 @@
|
||||
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)
|
||||
),
|
||||
|
||||
338
wasm-port/runtime/ui/simulation/index.html
Normal file
338
wasm-port/runtime/ui/simulation/index.html
Normal 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>
|
||||
239
wasm-port/runtime/ui/simulation/simulation-app.js
Normal file
239
wasm-port/runtime/ui/simulation/simulation-app.js
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user