Add desktop/server MP4 export pipeline, hardening, and render tests (v1.3.0)

Desktop MP4 export renders frames from a hidden BrowserWindow via
capturePage, pipes PNGs to a bundled ffmpeg (libx264/yuv420p/+faststart),
and writes the file through an atomic temp-file swap. It shares scene
validation (shared/renderValidation.cjs) and the render.html renderer with
the standalone Puppeteer server, which is hardened in parallel.

Render worker & main (electron/):
- abortable render loop with an inactivity watchdog and cancel plumbing
- atomic temp-file + rename so a cancel/failure never truncates a
  pre-existing target (#7); stdin EPIPE / early-exit / backpressure-hang
  handled (#8)
- HiDPI frames normalized to canvas size (#10); odd dimensions padded to
  even (#13, also server); backgroundThrottling disabled (#12)
- reject canvases larger than the display (#11); single-render guard;
  isolated render session that blocks network requests (#5)

Tests:
- Electron harnesses (scripts/verify-*.cjs) for capture, export, cancel,
  timeout, assets, background, capture-fidelity, resolution, odd-dims,
  aggregated by verify-desktop-render.cjs
- server smoke + e2e and shared validation unit tests
- headless hook (src/devtools/headless.ts) to run the in-app suite under
  browser/electron hosts; pixel-diff util + capture fixtures (test/)
- dev_docs plans: integration, fixes (#7-#13), and the e2e/capture strategy

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
thandClaude Opus 4.8 committed 2026-06-06 20:41:25 +02:00
1 parent 46f9f32aef
commit 518d43855b
41 files changed
+3907 -86

No files matched your search

+14 -1
View File
@@ -386,7 +386,20 @@
return true;
});
};
window.__animatrSeek=function(time){ drawFrame(time); };
window.__animatrSeek=function(time){
drawFrame(time);
// Resolve only after the compositor has had a chance to paint, so a
// subsequent capturePage()/screenshot() sees THIS frame and not the
// previous one. Without this wait, Electron's capturePage on a hidden
// window intermittently returns the prior frame (stale/off-by-one).
// setTimeout is an anti-hang fallback in case rAF is throttled while hidden.
return new Promise(function(res){
var done=false;
function finish(){ if(!done){ done=true; res(true); } }
requestAnimationFrame(function(){ requestAnimationFrame(finish); });
setTimeout(finish, 1000);
});
};
})();
</script>
</body>