Document source UI gates, explicit native live-plot clearing, and remaining parity boundaries. Fix pause/resume transport continuity, repeated stepping, coordinate previews, and spindle override state. Optimize large program snapshots and rendering, and cover the control flows with native Tcl, WASM, and browser checks. Validation: 22 native tests and source/browser checks pass. Worker/App regression: 113 of 123 pass; 10 pre-existing failures remain documented with no new failures.
37 lines
2.1 KiB
JavaScript
37 lines
2.1 KiB
JavaScript
import { AXIS_COMMANDS } from "./axis-contract.mjs";
|
|
|
|
export const COMMAND_ACTIONS = Object.freeze({ ...Object.fromEntries(Object.entries(AXIS_COMMANDS).map(([action, command]) => [command, action])), VISUAL_CLEAR: "vismachClear" });
|
|
|
|
export class AxisOperationController {
|
|
constructor(client) {
|
|
if (!client || typeof client.send !== "function") throw new Error("axis_operation_client_required");
|
|
this.client = client;
|
|
this.counter = 0;
|
|
this.trace = [];
|
|
}
|
|
|
|
nextOperationId(kind) {
|
|
this.counter += 1;
|
|
return `axis-${String(kind).toLowerCase().replace(/[^a-z0-9]+/g, "-")}-${String(this.counter).padStart(6, "0")}`;
|
|
}
|
|
|
|
async execute(kind, payload = {}, { state = {}, action = COMMAND_ACTIONS[kind] ?? null, gate = true } = {}) {
|
|
const operationId = this.nextOperationId(kind);
|
|
const effectiveGate = state?.gates?.[action] ?? { action, enabled: true, reason: "linuxcnc_wasm_worker_authority" };
|
|
this.trace.push(Object.freeze({ operation_id: operationId, kind, action, phase: "pending", reason: effectiveGate.reason }));
|
|
try {
|
|
const response = await this.client.send(kind, payload, operationId);
|
|
const responsePayload = response?.payload && typeof response.payload === "object" ? response.payload : {};
|
|
const workerBlocked = response?.kind === "ERROR" || ["blocked", "error"].includes(responsePayload.status);
|
|
this.trace.push(Object.freeze({ operation_id: operationId, kind, action, phase: workerBlocked ? "blocked" : "result", reason: responsePayload.gate_reason ?? responsePayload.message_key ?? responsePayload.code ?? response?.kind ?? null }));
|
|
if (workerBlocked) throw Object.assign(new Error(responsePayload.gate_reason ?? responsePayload.message_key ?? responsePayload.code ?? "axis_worker_rejected"), { response, operationId });
|
|
return Object.freeze({ operationId, response, gate: effectiveGate });
|
|
} catch (error) {
|
|
this.trace.push(Object.freeze({ operation_id: operationId, kind, action, phase: "error", reason: error?.message ?? String(error) }));
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
exportTrace() { return structuredClone(this.trace); }
|
|
}
|