Repository navigation
fix(core): words in a scene the preview mounts later keep their spaces - #7342
Conversation
Edit accuracy: accurate 267 (base branch 267), smooth 111 of thoseThe gate passes. Quarantined, measured but not gated (8)
Unstable (2)
|
There was a problem hiding this comment.
Approve at 5b41cc78. I found no blockers.
Fix. runSceneScripts now wraps runSceneScriptsLaidOut in layOutHiddenHosts with a try/finally. It only touches hosts that displayBeforeHide says the visibility pass hid. For those hosts it:
- puts back the authored display, keeping its priority;
- sets
opacity: 0 !importantandinertwhile the scripts run; - afterwards restores the author's opacity only if ours is still in place, removes
inertonly if the author didn't set it, and re-hides with the samestyle.display = "none"thathideByDisplaywrites, but only if the record still exists.
That last condition covers a visibility pass that runs during the font wait. If it shows the host, restoreDisplay deletes the record, so we don't hide it again. If it hides the host, the record is still there and we leave it hidden. The edit-swap path uses fresh importNode hosts with no record, so the filter leaves them alone. Only the mount path changes.
Fails without the fix (checked). I ran init.swapScenes.test.ts at the head with init.ts from main. lays out a scene the visibility pass hid by display, unseen, while its script builds its timeline fails with AssertionError: expected 'none false' to be 'block 0 true' (1 failed, 168 passed). With the fix, all 169 passed in each of 3 runs. vitest run src/runtime in core: 2858 passed.
Non-blocking notes:
- The laid-out window lasts through
runScriptsAfterFonts,waitForFonts, and asetTimeout(0), so frames can paint during it. The visibility pass hides by display only in-flow (static/relative/sticky) leaf clips. For one of those thatapplyClipLayoutdid not absolutize, its neighbours could shift for those frames. The PR already says this case was not measured. A follow-up test with an in-flow sibling would cover it. - If the playhead reaches the scene while its fonts are still loading, it stays at opacity 0 until the scripts finish. This is a short delay, not a wrong frame.
CI was still running when I reviewed, with no failed jobs so far.
…-com#7336-heygen-com#7358 Adopt heygen-com#7304 (without CLI renderDiagnostic, needs heygen-com#6725), heygen-com#7331, heygen-com#7332, heygen-com#7334, heygen-com#7337, heygen-com#7336, heygen-com#7339, heygen-com#7289, heygen-com#5594, heygen-com#6900, heygen-com#7322, heygen-com#7342, heygen-com#7343, heygen-com#7350 (onProgress only), heygen-com#7352, heygen-com#7348, heygen-com#7344, heygen-com#7353, heygen-com#7356 and heygen-com#7355. Defer heygen-com#5708 (needs heygen-com#6911) and heygen-com#7345 (READ_VERSION 4 vs 7); heygen-com#7307 not applicable. Re-read 14 moved pins, pin 6 new PRs (monitored 126). Make checkReportFile module-private and suppress the cross-package closeTestChrome duplicate for fallow. Co-authored-by: Miguel Ángel <miguel.sierra@heygen.com> Co-authored-by: SophiaHuangCc <129631224+SophiaHuangCc@users.noreply.github.com> Co-authored-by: Dante <duanjl.china@gmail.com> Co-authored-by: Muzzy5150 <muzzy5150yt@gmail.com> Co-authored-by: ump45nose <52391318+ump45nose@users.noreply.github.com> Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
What
In Studio's preview, words in a scene that mounts after load keep the spaces between them. Since 0.8.200 a line built as one span per word ("over the hill") showed as "overthe hill" once its scene mounted.
Why
Since #6189 the preview parks scenes away from the playhead and mounts them as it comes near. A mounted host keeps the
display:nonethe visibility pass wrote on its stand-in, and its scripts run while it is still hidden.A script that builds GSAP tweens on transform properties makes GSAP read each target's current transform. For an element it cannot lay out, GSAP 3.14's CSSPlugin moves the element to
<html>, reads it and puts it back withparent.insertBefore(el, el.nextElementSibling).nextElementSiblingskips text, so in<span>over</span> <span>the</span>the first word goes back after the space:" " over the, which renders as "overthe". The DOM stays that way until the scene is mounted again.Before #6189 every scene's script ran at load under the first-pass rule
visibility: hidden, which keeps layout, so GSAP never moved anything.Related work
Fixes a regression from #6189.
How
runSceneScriptsinpackages/core/src/runtime/init.tsis the one path both a scene mount and an edit's scene swap run scripts through. While a scene's scripts run, a host that the visibility pass hid by display is given its authored display back,opacity: 0 !importantandinert, so it is laid out but neither seen nor hit-tested (the HTML spec hit-tests an inert subtree as ifpointer-eventswerenone, and its children cannot opt back in). Afterwards the opacity is put back unless a script wrote its own,inertcomes off unless the author set it, and thedisplay:nonegoes back if the visibility pass still has the host hidden.Renders are not affected: they never park scenes.
What the unseen layout does to the rest of the frame
Measured in the real Studio preview on the project from the report, with this branch's CLI, sampling every animation frame across 8 scene mounts in 7 scenes (seeks into four scenes, then playback across two cuts):
position: absolute, so out of flow. No other displayed scene's layout box (offsetLeft,offsetTop,offsetWidth,offsetHeight) changed between the last frame before a mount and the mount itself.elementFromPointprobes over the preview, at the moment of each mount and in each of the 11 frames a mount spanned, never landed in the host being mounted (0 of 171).The words 0.8.x already joined
0.8.151 also shows some words without a space between them. Those are written that way: counting word spans whose previous sibling is another word span directly in each scene's source markup gives the same number per scene as 0.8.151 shows in the preview, in all 11 scenes (from 0 to 8 per scene, 34 in all). Tracing every DOM insert of a word span on 0.8.151 across load and a seek finds none, so no GSAP move is involved there. With this branch the scenes mounted at the playhead show the same counts as their markup.
Test plan
the preview's scene window > lays out a scene the visibility pass hid by display, unseen, while its script builds its timelinerecords the host's display, opacity andinertfrom inside the mounting scene's own script, then checks all three are put back.init.tsfrom main,lays out a scene the visibility pass hid by display, unseen, while its script builds its timelinefails withAssertionError: expected 'none false' to be 'block 0 true'. With the fix,init.swapScenes.test.tspasses (169 tests), 3 runs in a row.tsc --noEmitclean.Size
Small on purpose: one helper around the shared scene-script path plus its test.