diff --git a/coverage/xyzac-trt-tool-preview.report.json b/coverage/xyzac-trt-tool-preview.report.json index b98851f..4553f24 100644 --- a/coverage/xyzac-trt-tool-preview.report.json +++ b/coverage/xyzac-trt-tool-preview.report.json @@ -102,11 +102,11 @@ }, { "path": "web/react/src/App.tsx", - "sha256": "e8d8beee65031cdcb70e8c5ab5b59db90e6b93ee2ac229fc4894d6da07caaeed" + "sha256": "154f6d2cbe9003a6d217e4d77089a739b275767c66e36839b6b6ae64ed11cbbd" }, { "path": "web/react/src/styles.css", - "sha256": "0c523d532688228e29bcad6b828733a2f6b542cdb544584264c2f8d20ac9fad4" + "sha256": "1b0ee61a798338b6de901e60e17f71a7569eb04abda49f7eaa4a679cb22e6d2a" }, { "path": "wasm-core/src/sim_task.c", @@ -383,7 +383,7 @@ } }, "release_status": "STOP_RELEASE", - "report_sha256": "8fceb9e6f6af1089b405fd7b57aec455fc11b34ae5dc81b59448ae1469472a73", + "report_sha256": "54c3d3f24b7cd7ef58f7b6a480a8ba91b6d9309962368c85571711053eb85abc", "schema": "xyzac-trt-tool-preview-report.v1", "status": "verified", "task_id": "RTCP-10-TOOL-PREVIEW", diff --git a/coverage/xyzac-trt-wasm-authority-audit.report.json b/coverage/xyzac-trt-wasm-authority-audit.report.json index 22f9268..a8fdf10 100644 --- a/coverage/xyzac-trt-wasm-authority-audit.report.json +++ b/coverage/xyzac-trt-wasm-authority-audit.report.json @@ -205,7 +205,7 @@ "recovery": "Chromium SIGKILL/profile-reopen, corruption detection and Firefox OPFS round-trip are verified" }, "release_status": "STOP_RELEASE", - "report_sha256": "960537fe609868696cecacac63eb693adf11cc37406c7b098e653a914d0f5365", + "report_sha256": "24d166bbe8bf6ceac45c6f7c67cc2b9fccbc61c28f5412540193d955efe2d851", "scans": { "js_gcode_matches": [ { @@ -1496,7 +1496,7 @@ "files": [ { "path": "web/react/src/App.tsx", - "sha256": "e8d8beee65031cdcb70e8c5ab5b59db90e6b93ee2ac229fc4894d6da07caaeed" + "sha256": "154f6d2cbe9003a6d217e4d77089a739b275767c66e36839b6b6ae64ed11cbbd" }, { "path": "web/react/src/ThreeMachineView.tsx", @@ -1548,7 +1548,7 @@ }, { "path": "web/react/src/store.ts", - "sha256": "98a2634b0658d55bd1874c13196b7ac4ffffcdc9ff4246882b81147dad65523d" + "sha256": "2f415556da16d1ea4541d24e4a41fecfef316c7d45e5838f2cdf5d04ffa4d94e" }, { "path": "web/react/src/test_xyzac_threejs.mjs", diff --git a/coverage/xyzac-trt-wasm-only-release-gate.report.json b/coverage/xyzac-trt-wasm-only-release-gate.report.json index 3afe725..711477e 100644 --- a/coverage/xyzac-trt-wasm-only-release-gate.report.json +++ b/coverage/xyzac-trt-wasm-only-release-gate.report.json @@ -96,7 +96,7 @@ "failure_samples": [], "input": { "path": "coverage/xyzac-trt-web-wasm-parity.report.json", - "sha256": "f1664a422345d7974476a233de23bbebe784ef48d78422e66b3e44103295b47b" + "sha256": "090610e8912ff5e8e5537021674e4b6a8c043a148e1da1f9b44cebb42c2b468b" }, "known_blockers": [ "STOP_RELEASE is intentional until an owner authorizes scope expansion" @@ -104,7 +104,7 @@ "outputs": [], "release_decision": "selected_case_verified_but_release_stopped_by_scope_exclusions", "release_status": "STOP_RELEASE", - "report_sha256": "70ce954815cc9b7874bd7be3e0ed373fd9ad6ce9fb3b1af68ff9f70907c39b5a", + "report_sha256": "686b29b3118c1b9c60aceb2ea5d76fe83688fbad6b273ae270aa5114237d4e8e", "schema": "xyzac-trt-wasm-only-release-gate.v1", "status": "verified", "task_id": "RELEASE-00" diff --git a/coverage/xyzac-trt-web-wasm-parity.report.json b/coverage/xyzac-trt-web-wasm-parity.report.json index de9a476..6960bf4 100644 --- a/coverage/xyzac-trt-web-wasm-parity.report.json +++ b/coverage/xyzac-trt-web-wasm-parity.report.json @@ -101,12 +101,12 @@ "coverage/xyzac-trt-source-slice-inventory.report.json": "bf5f3fd36e1943e0dc42bafb2ed195fad147bcb3e56a8d208d7f01a3641829de", "coverage/xyzac-trt-startup.report.json": "56e040b645bcefc3d6c57244cb2d3aba16f421ff5137089d9c3336970dfd968b", "coverage/xyzac-trt-threejs.report.json": "5172d7118c555fca5f92bcb443b9c259ea0225346482558d4c33ef8a4f8da5cb", - "coverage/xyzac-trt-tool-preview.report.json": "1980f3dfad0471bdef5025aec230af5c5ba477ebd5ecccb9ada732d435465efd", + "coverage/xyzac-trt-tool-preview.report.json": "40d6d38a55a8d9b86601a0dc80a3edbbcdfd371207c058f944e64e979cce8c43", "coverage/xyzac-trt-trace-parity.report.json": "6c6b3500bc1189ff68513b74f0e5144879c3af7641349cb61737191303b76950", - "coverage/xyzac-trt-wasm-authority-audit.report.json": "57af1a779dc5d48b40b4a8576b7949aecaab2fd2eeaae7dfbbab35c06a254102", + "coverage/xyzac-trt-wasm-authority-audit.report.json": "3640d6d96e8152c70fde190491bc30586ef27498dbd5079278e402ee4fb0bfc4", "coverage/xyzac-trt-workflow.report.json": "9d0ed087b17ec14c906f4268d2d616dbc3800da03f731376b2ef9ae44ca5c6e7" }, - "report_sha256": "03d305c986a03aaaf6e04833b8e0d4095d840f89741582cd50be307f1aeed73c", + "report_sha256": "d940a5cda017837ea4c71885c698327115d8404e77bfbebb828079b805e80f66", "schema": "xyzac-trt-web-wasm-parity-report.v1", "source_coverage": { "axis_keyboard": 10, diff --git a/web/react/src/App.tsx b/web/react/src/App.tsx index 8ad9608..71ef5ea 100644 --- a/web/react/src/App.tsx +++ b/web/react/src/App.tsx @@ -87,15 +87,19 @@ function DroView({ axes, state, coordType, displayType, metric, setCoordType, se return PositionCoordinate setCoordType(event.target.value as "machine" | "relative")}>MachineRelativeDisplay setDisplayType(event.target.value as "actual" | "commanded")}>ActualCommandedUnits setMetric(event.target.value === "mm")}>mminchDisplayedActualRelative{labels.map((label, index) => { const actual = Number(axes[index] ?? 0); const target = Number(commanded[index] ?? actual); const relativeValue = Number(relative[index] ?? actual); const displayed = displayType === "commanded" ? target : actual; return {label}{format(coordType === "relative" ? relativeValue : displayed, index)}{format(actual, index)}{format(relativeValue, index)}; })}Units: {metric ? "mm" : "inch"} / deg{coordType} / {displayType}Tick: {state.tick}Hash: {state.snapshot?.state_hash?.slice(0, 12) || "-"}; } -function ProgramPane({ text, setText, activeGcodes, executionTrace, onLoad, onRun, onStep, onOpenFile, canLoad, canRun, canStep, currentLine, executionLine, executionLineText, diagnostic }: { text: string; setText: (text: string) => void; activeGcodes: string; executionTrace: Array<{ line: number; text: string; program_status?: string | null }>; onLoad: () => void; onRun: () => void; onStep: () => void; onOpenFile: () => void; canLoad: boolean; canRun: boolean; canStep: boolean; currentLine: number | null; executionLine: number | null; executionLineText: string; diagnostic: string | null }) { +function ProgramPane({ text, setText, activeGcodes, executionTrace, programStatus, onLoad, onRun, onStep, onOpenFile, canLoad, canRun, canStep, currentLine, executionLine, executionLineText, diagnostic }: { text: string; setText: (text: string) => void; activeGcodes: string; executionTrace: Array<{ line: number; text: string; program_status?: string | null }>; programStatus: string; onLoad: () => void; onRun: () => void; onStep: () => void; onOpenFile: () => void; canLoad: boolean; canRun: boolean; canStep: boolean; currentLine: number | null; executionLine: number | null; executionLineText: string; diagnostic: string | null }) { const lines = text.split("\n"); - const mainLine = currentLine && currentLine > 0 && currentLine <= lines.length ? currentLine : null; + const executing = ["running", "paused"].includes(programStatus.toLowerCase()); + const mainLine = executing && currentLine && currentLine > 0 && currentLine <= lines.length ? currentLine : null; + const executedLines = new Set(executionTrace.map((entry) => entry.line)); + const editorRef = useRef(null); + const highlightsRef = useRef(null); const traceRef = useRef(null); useEffect(() => { const node = traceRef.current; if (node) node.scrollTop = node.scrollHeight; }, [executionTrace]); - return {lines.map((_, index) => {index + 1})} setText(event.target.value)} spellCheck={false} aria-invalid={Boolean(diagnostic)} aria-label="G-code program" />{executionLine ? `Executing source line ${executionLine}` : "Execution idle"}{executionLineText || "-"}Execution trace{executionTrace.slice(-8).map((entry, index) => L{entry.line}{entry.text || "-"})}; + return {lines.map((_, index) => { const line = index + 1; const className = line === mainLine ? "current-line" : executedLines.has(line) ? "completed-line" : ""; return {line}; })}{lines.map((_, index) => { const line = index + 1; const className = line === mainLine ? "current-line" : executedLines.has(line) ? "completed-line" : ""; return ; })} setText(event.target.value)} onScroll={() => { if (editorRef.current && highlightsRef.current) { highlightsRef.current.scrollTop = editorRef.current.scrollTop; highlightsRef.current.scrollLeft = editorRef.current.scrollLeft; } }} spellCheck={false} aria-invalid={Boolean(diagnostic)} aria-label="G-code program" />{executionLine ? `Executing source line ${executionLine}` : "Execution idle"}{executionLineText || "-"}Execution trace{executionTrace.slice(-8).map((entry, index, entries) => { const isCurrent = executing && index === entries.length - 1; return L{entry.line}{entry.text || "-"}; })}; } function DiagnosticsPanel({ state, gates }: { state: typeof INITIAL_STATE; gates: Record }) { @@ -240,7 +244,7 @@ export function App() { Manual Control [F3] setLeftTab("mdi")}>MDI [F5] setLeftTab("diagnostics")}>Diagnostics setRightTab("preview")}>Preview setRightTab("dro")}>DRO Jog Speed:50%Feed Override:{Math.round((state.snapshot?.overrides?.feed ?? 1) * 100)}% execute("SET_OVERRIDE", { feed: Number(event.target.value) / 100 })} />Spindle Override:{Math.round((state.snapshot?.overrides?.spindle?.[0] ?? 1) * 100)}%{(["path", "live", "limits", "velocity", "offsets", "extents"] as const).map((layer) => setLayers((current) => ({ ...current, [layer]: event.target.checked }))} /> {layer})} - { setPlotCleared(false); execute("PROGRAM_LOAD", { source: programText, path: PROGRAM_PATH }); }} onRun={runProgram} onStep={() => execute("PROGRAM_STEP")} canLoad={gates.load.enabled} canRun={gates.run.enabled} canStep={gates.step.enabled} currentLine={currentLine} executionLine={executionLine} executionLineText={executionLineText} diagnostic={state.error?.code ?? null} /> + { setPlotCleared(false); execute("PROGRAM_LOAD", { source: programText, path: PROGRAM_PATH }); }} onRun={runProgram} onStep={() => execute("PROGRAM_STEP")} canLoad={gates.load.enabled} canRun={gates.run.enabled} canStep={gates.step.enabled} currentLine={currentLine} executionLine={executionLine} executionLineText={executionLineText} diagnostic={state.error?.code ?? null} /> ; diff --git a/web/react/src/store.ts b/web/react/src/store.ts index d61cdb1..a54a586 100644 --- a/web/react/src/store.ts +++ b/web/react/src/store.ts @@ -63,7 +63,8 @@ function projection(result: Record, message: MessageEnvelope, p .map((entry) => ({ line: Number(entry.line), text: String(entry.text ?? ""), program_status: entry.program_status ? String(entry.program_status) : null })) .filter((entry) => Number.isSafeInteger(entry.line) && entry.line > 0) : []; - const executionTrace = trace.length ? [...previous.executionTrace, ...trace].slice(-32) : previous.executionTrace; + const resetExecutionTrace = ["PROGRAM_LOAD", "PROGRAM_RESTART"].includes(String(result.command ?? "")); + const executionTrace = resetExecutionTrace ? trace.slice(-32) : trace.length ? [...previous.executionTrace, ...trace].slice(-32) : previous.executionTrace; const delta = result.motion_sample as { tick?: number; pose?: Record; joints?: number[]; active_segment_id?: number; active_progress?: number } | undefined; if (!panel && delta && previous.snapshot) { const pose = delta.pose ?? {}; diff --git a/web/react/src/styles.css b/web/react/src/styles.css index 8d52edf..1c03220 100644 --- a/web/react/src/styles.css +++ b/web/react/src/styles.css @@ -181,9 +181,15 @@ output { font: 12px ui-monospace, monospace; text-align: right; } .program-editor-wrap { display: grid; grid-template-columns: 34px minmax(0, 1fr); min-width: 0; min-height: 0; } .program-gutter { display: flex; flex-direction: column; align-items: end; padding: 6px 6px 6px 0; overflow: hidden; border-right: 1px solid #c2c2c2; color: #888; background: #e4e4e4; font: 12px/1.45 ui-monospace, monospace; user-select: none; } .program-gutter span { height: 17px; } -.program-gutter span.current-line { color: #174d84; background: #c8d7e8; font-weight: 700; } -.program-editor-wrap textarea { width: 100%; height: 100%; min-height: 180px; padding: 6px 8px; resize: none; border: 0; outline: none; background: #fff; color: #111; font: 12px/1.45 ui-monospace, monospace; tab-size: 4; } -.program-execution-line { display: grid; grid-column: 1; grid-row: 2; grid-template-columns: max-content minmax(0, 1fr); align-items: baseline; gap: 10px; min-width: 0; padding: 5px 8px; border-top: 1px solid #9aa8b4; border-bottom: 1px solid #9aa8b4; background: #e9f1f8; color: #173b5f; font: 12px/1.35 ui-monospace, monospace; } +.program-gutter span.current-line { color: #5a4100; background: #ffe066; font-weight: 700; } +.program-gutter span.completed-line { color: #666; background: #e6e6e6; } +.program-editor-input { position: relative; min-width: 0; min-height: 0; overflow: hidden; background: #fff; } +.program-line-highlights { position: absolute; inset: 0; z-index: 0; padding: 6px 8px; overflow: hidden; pointer-events: none; font: 12px/1.45 ui-monospace, monospace; } +.program-line-highlights span { display: block; height: 17px; margin: 0 -8px; } +.program-line-highlights span.completed-line { background: #eeeeee; } +.program-line-highlights span.current-line { background: #ffe066; } +.program-editor-input textarea { position: relative; z-index: 1; width: 100%; height: 100%; min-height: 180px; padding: 6px 8px; resize: none; border: 0; outline: none; background: transparent; color: #111; font: 12px/1.45 ui-monospace, monospace; tab-size: 4; } +.program-execution-line { display: grid; grid-column: 1; grid-row: 2; grid-template-columns: max-content minmax(0, 1fr); align-items: baseline; gap: 10px; min-width: 0; padding: 5px 8px; border-top: 1px solid #9aa8b4; border-bottom: 1px solid #9aa8b4; background: #ffe066; color: #3f2d00; font: 12px/1.35 ui-monospace, monospace; } .program-execution-line span { font-weight: 700; white-space: nowrap; } .program-execution-line code { min-width: 0; overflow: hidden; color: #111; text-overflow: ellipsis; white-space: nowrap; } .program-execution-trace { grid-column: 1; grid-row: 3; min-width: 0; padding: 4px 8px; overflow: auto; border-bottom: 1px solid #9aa8b4; background: #f4f7fa; color: #253746; font: 11px/1.3 ui-monospace, monospace; } @@ -192,6 +198,9 @@ output { font: 12px ui-monospace, monospace; text-align: right; } .program-execution-trace li { display: grid; grid-template-columns: 42px minmax(0, 1fr); gap: 8px; min-width: 0; } .program-execution-trace li span { color: #174d84; font-weight: 700; } .program-execution-trace li code { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +.program-execution-trace li.completed-line { padding: 1px 4px; background: #e8e8e8; color: #666; } +.program-execution-trace li.executing-line { padding: 1px 4px; background: #ffe066; color: #111; font-weight: 700; } +.program-execution-trace li.executing-line span { color: #6b4b00; } .program-side { display: flex; grid-column: 2; grid-row: 1 / span 3; flex-direction: column; gap: 12px; padding: 8px; border-left: 1px solid #aaa; background: #d9d9d9; } .program-actions { display: flex; flex-wrap: wrap; gap: 5px; } .active-gcodes-label { display: grid; gap: 5px; color: #333; }
{executionLineText || "-"}
{entry.text || "-"}