mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-10-30 17:27:09 +00:00
* perf(studio): ask each ancestor once per off-canvas rebuild, not once per element The dashed off-canvas markers are rebuilt whenever anything in the preview changes, which during playback or a scrub is several times a second. Most of what a rebuild asked about an element was really a question about its ANCESTORS: does each node up to the root render, what does each contribute to the composed transform, which node is the source-file boundary. Two siblings share their whole chain, so a preview of a thousand elements asked the platform the same questions about the same ancestors a thousand times over. A rebuild now threads one measure pass through the walk, and each node answers once. On a 1689-element carousel that takes computed-style reads from 11,159 to 2,526 per rebuild and the rebuild itself from 59 to 12-18 ms per seek, with the rendered marker set byte-identical. The pass is deliberately not a cache. A cache would have to say what could have changed since last time, and for a measurement the answer is anything: an image finishing decode, a font swapping in, a transition frame, a container query, an inserted stylesheet rule all move an element's box with nothing written to the DOM and no record to invalidate on. A pass lives inside one synchronous measurement that only reads, so nothing can move under it, and it is dropped when the measurement ends. Every rebuild still measures every element. Layout reads are untouched on purpose: each element still takes its own client rect every rebuild, because that is the read that cannot be shared and must not be remembered. * test(studio): give one PropertyPanel case the file's own render timeout Every other render test in that file passes RENDER_TIMEOUT_MS; this it.each was left on vitest's 5s default and times out when the whole suite runs on a loaded machine. Unrelated to any behaviour: it passes on either side of the change when the machine is quiet, and fails in a full-suite run on either side when it is not. * refactor(studio): share the source-boundary walk and dedupe the rebuild fixtures Three follow-ups from review of the measure pass, none of them behaviour: The source boundary was memoized against the element that asked for it, so two siblings still walked their whole chain separately and only the answer was reused. It now memoizes every node on the way up, like the other two walks: an element's boundary IS its parent's unless the element is one itself. `isElementVisibleThroughAncestors` returns what it always returned, but it now resolves top-down, so the set of nodes it takes a style read for is different (smaller for a subtree hidden near the root). Three callers use it with no memo, so that is written down next to it rather than left to be discovered. The three rebuild fixtures in the indicator tests each carried their own copy of the iframe/overlay/layout-stub scaffolding, which is four clone groups and 126 duplicated lines. One `mountPreview` helper now serves all of them and the pre-existing selector-index fixture too. * test(studio): pin the measure pass to one rebuild through an ancestor-derived input The existing no-mutation-record test varies an element's own box, and the pass never memoizes a box — so hoisting the pass to module scope, which is exactly the mistake that would reintroduce cross-rebuild staleness, left every test green. This one fades a wrapper between two rebuilds and asserts the marker under it goes away. Visibility through ancestors IS memoized, so the pass surviving the rebuild serves the stale answer: with `createOverlayMeasurePass()` hoisted the suite exits 1 on this test alone, and exits 0 at head.