Align AXIS control flow and fix pause and program rendering

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.
This commit is contained in:
2026-09-08 10:46:27 +08:00
parent 845075f4a3
commit bade560372
55 changed files with 2495 additions and 286 deletions

View File

@@ -88,19 +88,23 @@ const stopped = await wait((value) => value.operation === "Operation IDLE" && va
if (stopped.epoch !== running.epoch || stopped.liveCount < pausedStable.liveCount || stopped.previewCount !== loadedLong.previewCount) throw new Error(`stop_cleared_path:${JSON.stringify(stopped)}`);
const loadedShort = await load("live-short.ngc", shortSource);
if (loadedShort.epoch <= stopped.epoch || loadedShort.liveCount !== 0) throw new Error(`load_did_not_clear_live_path:${JSON.stringify({ stopped, loadedShort })}`);
if (loadedShort.epoch !== stopped.epoch || loadedShort.liveCount < stopped.liveCount) throw new Error(`open_cleared_native_live_path:${JSON.stringify({ stopped, loadedShort })}`);
await click(main.locator('button[aria-label="Begin executing current file [R]"]'), "run_short");
const completed = await wait((value) => value.program === "completed" && value.operation === "Operation IDLE" && value.liveCount >= 2, "short_completed");
await page.waitForTimeout(1000);
const completedStable = await state();
if (completedStable.epoch !== completed.epoch || completedStable.liveCount < completed.liveCount || completedStable.previewCount !== loadedShort.previewCount) throw new Error(`completion_cleared_path:${JSON.stringify({ completed, completedStable })}`);
await click(main.locator('[data-axis-control="toolbar-reload"]'), "reload_same_file");
await wait((value) => value.program === "loaded" && value.loading === "false", "same_file_reloaded");
const reloaded = await state();
if (reloaded.epoch !== completedStable.epoch || reloaded.liveCount < completedStable.liveCount) throw new Error(`reload_cleared_native_live_path:${JSON.stringify(reloaded)}`);
await click(main.locator('button[aria-label="Begin executing current file [R]"]'), "rerun_short");
const rerun = await wait((value) => value.epoch > completedStable.epoch && value.liveCount < completedStable.liveCount, "rerun_new_epoch");
const rerun = await wait((value) => value.program === "running" && value.epoch === completedStable.epoch && value.liveCount >= completedStable.liveCount, "rerun_retains_native_epoch");
await wait((value) => value.program === "completed" && value.operation === "Operation IDLE", "rerun_completed");
const replacement = await load("live-replacement.ngc", replacementSource);
if (replacement.epoch <= rerun.epoch || replacement.liveCount !== 0 || replacement.previewCount !== 1) throw new Error(`replacement_lifecycle_invalid:${JSON.stringify({ rerun, replacement })}`);
if (replacement.epoch !== rerun.epoch || replacement.liveCount < rerun.liveCount || replacement.previewCount !== 1) throw new Error(`replacement_lifecycle_invalid:${JSON.stringify({ rerun, replacement })}`);
await click(main.locator('button[aria-label="Begin executing current file [R]"]'), "run_replacement");
await wait((value) => value.liveCount >= 1 && Number(value.line) > 0, "replacement_trace");
await wait((value) => value.operation === "Operation IDLE", "replacement_idle");
@@ -109,6 +113,15 @@ await click(main.locator('button[aria-label="Clear live plot [Ctrl-K]"]'), "clea
const cleared = await wait((value) => value.epoch > beforeClear.epoch && value.liveCount <= 1, "cleared_live_plot");
if (cleared.previewCount !== beforeClear.previewCount) throw new Error(`clear_removed_static_preview:${JSON.stringify({ beforeClear, cleared })}`);
await load("live-key-clear.ngc", shortSource);
await click(main.locator('[data-axis-control="toolbar-run"]'), "run_for_keyboard_clear");
await wait((value) => value.operation === "Operation IDLE" && value.program === "completed" && value.liveCount > 2, "regenerate_live_trace");
const beforeKeyClear = await state();
await page.evaluate(() => document.activeElement?.blur());
await page.keyboard.press("Control+k");
const keyCleared = await wait((value) => value.epoch === beforeKeyClear.epoch + 1 && value.liveCount === 0, "ctrl_k_clears_live_trace");
if (keyCleared.previewCount !== beforeKeyClear.previewCount || keyCleared.queue !== beforeKeyClear.queue || keyCleared.program !== beforeKeyClear.program) throw new Error("ctrl_k_changed_program_or_queue");
const desktopPixels = await canvasPixels(page);
if (desktopPixels.changed < 100) throw new Error(`desktop_canvas_blank:${JSON.stringify(desktopPixels)}`);
await page.screenshot({ path: "/tmp/xyzac-live-path-lifecycle-desktop.png", fullPage: true });
@@ -118,5 +131,6 @@ const mobilePixels = await canvasPixels(page);
if (mobilePixels.changed < 100) throw new Error(`mobile_canvas_blank:${JSON.stringify(mobilePixels)}`);
await page.screenshot({ path: "/tmp/xyzac-live-path-lifecycle-mobile.png", fullPage: true });
console.log(JSON.stringify({ schema: "xyzac-live-path-lifecycle-browser.v1", status: browserErrors.length ? "failed" : "verified", lifecycle: { loadedLong, running, paused: pausedStable, resumed, stopped, loadedShort, completed: completedStable, rerun, replacement, beforeClear, cleared }, canvas_pixels: { desktop: desktopPixels, mobile: mobilePixels }, browser_errors: browserErrors, screenshots: ["/tmp/xyzac-live-path-lifecycle-desktop.png", "/tmp/xyzac-live-path-lifecycle-mobile.png"] }));
console.log(JSON.stringify({ schema: "xyzac-live-path-lifecycle-browser.v1", status: browserErrors.length ? "failed" : "verified", lifecycle: { loadedLong, running, paused: pausedStable, resumed, stopped, loadedShort, completed: completedStable, reloaded, rerun, replacement, beforeClear, cleared, beforeKeyClear, keyCleared }, canvas_pixels: { desktop: desktopPixels, mobile: mobilePixels }, browser_errors: browserErrors, screenshots: ["/tmp/xyzac-live-path-lifecycle-desktop.png", "/tmp/xyzac-live-path-lifecycle-mobile.png"] }));
if (browserErrors.length) process.exitCode = 1;
await browser.close();