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>
README
------
Initial README.md at repo root with the existing src/assets/logo.png
header, the editor screenshot from src/assets/ruwio_screenshot.png,
a feature-highlights list, quickstart, stack, project layout, and
the in-app test runner shortcut (Ctrl+Shift+Alt+T).
Effects panel merge
-------------------
Wiggle and Kaleidoscope used to live in two separate collapsible
sections. Now they share one "Effects" section with a thin border
between the two sub-blocks (`.effect-block + .effect-block`),
keeping their independent toggles + controls. Same change for the
ClipInstance variant. Section preference key flows through one
`effects` slot instead of two.
Layer kaleido pivot
-------------------
Until now only ClipInstance kaleido had a separate pivot;
Layer kaleido was locked to the layer's own (x, y). Layers now
get optional `kaleidoPivotX/Y` too, mirroring the ClipInstance
contract.
- types/scene.ts: optional pivot fields on Layer.
- lib/kaleido.ts: layerKaleidoPivot + layerArmTransform helpers
computing per-arm world (x, y, rotation) around the override.
- lib/render.ts: drawLayers + drawMaskShape route through
layerArmTransform.
- lib/exportPlayer.ts: byte-for-byte JS mirror layerArmTf inside
the engine source. RUNTIME_VERSION 32 → 33.
- components/CanvasStage.vue: withK now takes the layer +
computes rotated x/y/rotation per arm; honours the
liveKaleidoPivot ref so a drag updates all copies in real
time. Generic kaleidoPivotHandle handles both clip-instance
and layer entities (KaleidoPivotEntity discriminated union);
manual window-level pointermove drives the drag because
Konva's `draggable: true` didn't fire dragmove for shapes
inside the clipFunc'd canvas group here.
- stores/scene.ts: setLayerKaleidoPivot(id, patch | null) with
null-clear semantics.
- components/PropertiesPanel.vue: two pivot X/Y inputs in a
`.row` inside the layer kaleido sub-block, defaulting to the
sampled position. All kaleido inputs (count, offset, pivot)
switched from @change to @input so the stage repaints live
while the user types or spins the value.
Test fixes
----------
- "PropertiesPanel kaleido offset writes back through the mode"
dispatched a synthetic 'change' event; the input now binds to
@input. Updated to dispatch 'input'.
- "PropertiesPanel: Wiggle section toggles the wiggle field" —
section open-state persists across tests via the panel's
localStorage pref. Test now resets the key before mount and
only clicks the header when the body isn't already shown,
then restores the prior value.
- "PropertiesPanel: layer kaleido pivot inputs render + write
back live" used a global value-50 filter that also matched the
Transform section's X/Y inputs at the sampled position (50, 50).
Scoped the search to the Effects section's body + its `.row`.
Tests added (6 new under "layer kaleido pivot — separate orbit centre"):
- layerArmTransform math: defaults vs explicit override.
- store roundtrip + null-clear of setLayerKaleidoPivot.
- drawSceneFrame integration: 4 cardinal pixel checks for a
20×20 red rect at (160, 100), pivot (100, 100), 4 arms —
sampled at the rotated body centres.
- PropertiesPanel: two pivot inputs render at the sampled
position; @input writes pivotX while pivotY stays default.
- CanvasStage smoke: pivot handle paths render when a kaleido-
on layer is selected (plus toggle-off cleanup).
- Engine source carries layerArmTf and bumps RUNTIME_VERSION
to '33'. Earlier '32' assertions in the kaleido + wiggle
suites also bumped to '33'.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>