fix(cli): sweep_static geometry fingerprint now covers SVG stroke/dash motion - #3914
Open
miga-heygen wants to merge 2 commits into
Open
miga-heygen wants to merge 2 commits into
miga-heygen wants to merge 2 commits into
Conversation
Contributor
Author
|
Addressed in b8abfc7. |
miga-heygen
force-pushed
the
fix/sweep-static-svg-dash
branch
from
September 14, 2026 18:26
f35dfe3 to
b8abfc7
Compare
|
Preview deployment for your docs. Learn more about Mintlify Previews.
💡 Tip: Enable Automations to automatically generate PRs for you. |
miga-heygen
changed the base branch from
main
to
magi/fix-sweep-static-text-fingerprint
September 14, 2026 18:26
Contributor
Author
miga-heygen
force-pushed
the
fix/sweep-static-svg-dash
branch
from
September 15, 2026 01:19
b8abfc7 to
582018d
Compare
Contributor
Author
miga-heygen
force-pushed
the
fix/sweep-static-svg-dash
branch
from
September 15, 2026 02:24
582018d to
5f4196b
Compare
A "draw the line in" SVG entrance animates stroke-dasharray / stroke-dashoffset on a shape whose geometry never changes, so the box+opacity channels read it as a frozen timeline and `hyperframes check` false-positives sweep_static on a visibly animating composition. - Append strokeDashChannel to ELEMENT_CHANNELS: hashes the computed dash pattern and offset for SVG geometry elements with a painted stroke; `none` / all-zero patterns, unpainted (none, transparent, zero-width, stroke-opacity 0) strokes and non-SVG elements contribute "". - Widen isVisibleElement for stroked geometry with one degenerate bbox axis: Chromium's getBoundingClientRect for SVG shapes excludes the stroke, so a straight horizontal or vertical connector reports 0 height or 0 width and was dropped by the box gate — the most common shape for this animation. motion-sample's `visible` bit for such connectors is corrected as a consequence. - Treat <defs>, <clipPath> and <symbol> as never-painted subtrees in the hidden-subtree walk (display is not inherited, so descendants are hidden only through the ancestor walk) rather than relying on one engine's empty-box behaviour for their descendants. Tests: happy-dom pins for the degenerate-bbox connector (moves / frozen), a display:none ancestor, <defs>/<clipPath> content and a stroke-opacity:0 decoy; real-Chromium pins for the connector draw-in and the ignored cases. Co-Authored-By: Miguel Ángel <miguel.sierra@heygen.com>
miga-heygen
force-pushed
the
fix/sweep-static-svg-dash
branch
from
September 15, 2026 02:34
5f4196b to
95624bc
Compare
Contributor
Author
|
Updated as 95624bc: SVG container set and guard. |
Follow-up coverage for the SVG stroke-dash motion channel:
- stroke-dasharray changes at a fixed stroke-dashoffset now change both
the exact sweep signature and the quantized liveness signature
(happy-dom and Chromium)
- one table row per guard: stroke-opacity 0, a transparent stroke,
stroke-width 0, and all-zero dash lists ("0", "0 0") each leave the
signature unchanged under a moving offset
- a dashed stroke inherited by <g> / <text> (not SVGGeometryElement) is
not signed
- a source-parity test asserts UNPAINTED_SVG_CONTAINERS and layout-audit's
CONNECTOR_SKIP_CONTAINERS name the same containers
- the container comment no longer claims motion inside a referenced
<mask> / <pattern> is attributed to the referencing element; its own
channels are, the referenced content has none
Mutation-checked: hashing the offset only, dropping each stroke/dash
guard, widening the element filter to SVGElement, and removing a
container name each fail exactly their own rows in both suites.
Co-Authored-By: Miguel Ángel <miguel.sierra@heygen.com>
Contributor
Author
|
Addressed in 434d1e0. |
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
hyperframes check's seek-audit firessweep_static("Timeline did not advance under seek") when the per-sample motion signature is byte-identical across the whole run. An SVGstroke-dasharray/stroke-dashoffset"draw the line in" entrance — the standard way to animate a connector or diagram line — moves no bounding box and no opacity, so a genuinely animating composition using it was flagged as a frozen timeline.With #3707 the signature has a single owner (
packages/cli/src/commands/motion-signature.browser.js,ELEMENT_CHANNELS), shared by the frozen-sweep guard andkeepsMovingliveness. This PR:strokeDashChanneltoELEMENT_CHANNELS. Hashes the computed dash pattern and offset for SVG geometry elements (SVGGeometryElement: path/circle/ellipse/rect/line/polyline/polygon) with a painted stroke.noneand all-zero patterns (which render solid), unpainted strokes (none, transparent, zero width,stroke-opacity: 0) and non-SVG elements contribute"", so ordinary compositions gain no payload.isVisibleElementfor stroked geometry with one degenerate bbox axis. Chromium'sgetBoundingClientRectfor SVG shapes is the object bounding box without the stroke: a straight horizontal or vertical connector reports 0 height or 0 width regardless ofstroke-width, so the box gate dropped exactly the most common shape for this animation. A stroked shape with one non-degenerate axis now counts as visible. As a consequencemotion-sample'svisiblebit for such connectors is corrected too (it previously reported them invisible).<defs>,<clipPath>,<mask>,<pattern>,<marker>and<symbol>as never-painted subtrees in the existing hidden-subtree walk (the same container listlayout-audit.browser.jsskips for connectors).displayis not inherited — a child of adisplay:noneparent computesdisplay: block— so descendants are excluded only through the ancestor walk, which now also starts at these containers rather than relying on one engine's empty-box behaviour for their descendants. The match is namespaced (instanceof SVGElement), so an HTML element that merely shares a name is not pruned.Test plan
motion-signature.browser.test.ts(happy-dom, 43 passing): dash-offset moves on a 290×0 connector change the signature, and so does astroke-dasharraychange at a fixed offset (both the exact sweep and the quantized liveness signature); identical scene hashes identically; dash motion under adisplay:noneancestor and inside<defs>/<clipPath>/<mask>/<pattern>/<marker>/<symbol>is ignored; one table row per channel guard —stroke-opacity: 0,stroke: rgba(0, 0, 0, 0),stroke-width: 0,stroke-dasharray: 0and0 0(solid) — leaves the signature unchanged under a moving offset; a dashed stroke inherited by a<g>/<text>(notSVGGeometryElement) is not signed; a fade inside an HTML element named<defs>still changes the signature. A source-parity test extractsUNPAINTED_SVG_CONTAINERSand layout-audit'sCONNECTOR_SKIP_CONTAINERSfrom the two files and asserts the name sets are equal (case-insensitive), so the hand-kept copy cannot drift silently. Fixtures use the real Chromium bbox of a horizontal stroked path (height 0) so they pin the widened gate, not the pre-existing box path.motion-signature.browser.chromium.test.ts(real Chromium, 44 passing): astroke-dashoffsetdraw-in and a fixed-offsetstroke-dasharraychange on a straight connector each change both the sweep and the liveness signature; dash motion underdisplay:noneand inside all six unpainted containers leaves both identical; the same five guard rows as the happy-dom suite (against Blink's computed values —stroke: transparentcomputes torgba(0, 0, 0, 0)) and the<g>/<text>case leave both identical; a fade inside an HTML<defs>element changes both signatures.motion-sample.browser.test.ts(happy-dom, 8 passing) unchanged.<path>reports a 290×0 box;display:nonedescendants computedisplay: inline; descendants of all six containers (<defs>,<clipPath>,<mask>,<pattern>,<marker>,<symbol>) report 0×0 whilecheckVisibilityreturns true for them, so the container rule is what excludes them by intent rather than the box gate; computedstroke-dashoffsetis rounded (144.99999999px→145px), so the signature cannot vary on its own at a fixed seek.stroke: transparent/stroke-opacity: 0counting as painted;stroke-dasharray: 0(solid) counting as a pattern; tag-name matching hitting HTML unknown elements (replaced byinstanceof SVGGeometryElement).<mask>/<pattern>/<marker>missing from the never-painted set although they meet its definition (and the widened bbox gate would otherwise admit a straight connector inside them); the tag-name match lacking a namespace guard.stroke-width > 0guard, or the all-zero-is-solid guard, widening the element filter toSVGElement, and removing a name fromUNPAINTED_SVG_CONTAINERSeach fail exactly their own row(s) in both suites.oxlint,oxfmt --check, andtsc --noEmitclean forpackages/cli. The happy-dom suites cannot be collected directly in the local sandbox under bun (No such built-in module: node:from the@vitest-environment happy-domloader); they were run with--environment nodeplus a setup file that populates globals from a happy-domWindow, and pass. CI runs them as-is.Deliberately out of scope: dashing on
<text>/<use>(no channel today); other "moves nothing else" SVG reveals (fill-opacity,stroke-opacity, gradientstop-opacity); animation of the content of a<mask>/<pattern>/<clipPath>/<marker>referenced by an otherwise static element (it changes pixels through the referencing element, whose own channels do not move — and Blink gives that content 0×0 boxes, so it was never signed); andpathLengthattribute animation (rescales the dash pattern with no computed-style change) — this closes the stroke/dash gap only.