Skip to content

Fork patch: Probe untraced media src writes - #13

Draft
valeriangalliat wants to merge 1 commit into
mainfrom
patch/probe-untraced-media-src
Draft

valeriangalliat wants to merge 1 commit into
mainfrom
patch/probe-untraced-media-src

Conversation

@valeriangalliat

@valeriangalliat valeriangalliat commented Nov 9, 2026 •

Copy link
Copy Markdown
Collaborator

🤖 AI agent: Arcade fork patch. Do not merge.

This draft shows patch/probe-untraced-media-src against main, the upstream mirror, so the patch has one page for its diff, its revisions and its reason. Arcade's /hyperframes-update rebases the branch onto main and cherry-picks it onto arcade; this PR is not part of that flow. When a rebuild changes the patch, its git range-diff against the previous sync/<date>/probe-untraced-media-src tag is posted as a comment.

Why

The renderer extracts video frames from the static src unless a trigger makes it load the page. hasRuntimeMediaChanges only follows src writes whose target it can resolve statically (getElementById or querySelector('<literal>')). A write in a loop, like root.querySelectorAll("[data-media-key]").forEach(el => { el.src = S[key]; }), was dropped by the parser, so a timed video exported its authored clip while preview played the swapped one.

The parser now keeps such writes with selector: null, and a null selector loads the page when the document has any <video> or <audio>. Lint skips null selectors, so its output is unchanged.

Untimed videos already load the page through the auto-start trigger, which is why this only shows with data-start.

Review notes

  • MediaSrcMutation.selector becomes string | null in @hyperframes/parsers, a breaking type change for external consumers. Arcade doesn't import it.
  • With media on the page, any untraced x.src = … loads the page, even one aimed at an <img> or a plain object. That costs time, not correctness.
  • Still missed: external scripts, Object.assign(el, { src }), writes after a setTimeout.
  • Arcade side: once this is pinned, the renderer uses URLs from scene settings JSON, which Arcade's pre-export alt=media repair doesn't cover yet. That repair needs extending before the pin ships.

Verification

  • probeStage.test.ts 71/71, two new tests: a loop write with a video probes (fails without the source change), a loop write on images only doesn't.
  • Lint media.test.ts 137/137. tsc, oxfmt, oxlint and lefthook clean.
  • Arcade render corpus on prod's a318cd008 plus an earlier revision that included this change: 22/22. Corpus row setting-src-keyed-loop (ArcadeHQ/arcade#13159) fails on a318cd008 alone and passes with it.

Upstream

Issue filed: heygen-com/hyperframes#7504, with a repro. No upstream PR. Upstream may prefer teaching the parser the forEach shape over "unknown target means maybe"; the issue asks.

Drop when

Upstream probes media src writes it cannot resolve statically.

Arcade

ARC-17431 · ArcadeHQ/arcade#13159 · ArcadeHQ/arcade#13115

@github-actions

github-actions Bot commented Nov 9, 2026 •

Copy link
Copy Markdown

Edit accuracy: accurate 271 (base branch 271), smooth 130 of those

The gate passes.
Smoothness is reported in the artifact, not gated. A case fails only if it fails 2 of 3 runs.

Quarantined, measured but not gated (0)

Unstable (1)

  • seqplainkeys-none-pct-r0-nested-z100: tracking 0.05, pressJump 0, drop 0, reload 0, render 0.02, renderKey -, undo false, teleport true / tracking 0.05, pressJump 0, drop 0, reload 0, render 0.02, renderKey -, undo true, teleport true / tracking 0.05, pressJump 0, drop 0, reload 0, render 0.02, renderKey -, undo true, teleport true

@valeriangalliat
valeriangalliat force-pushed the patch/probe-untraced-media-src branch from 749a497 to 193db26 Compare November 9, 2026 23:41
@valeriangalliat valeriangalliat changed the title Fork patch: Probe untraced media src writes Fork patch: Probe untraced media src writes and changed data-var-src media Nov 9, 2026
@valeriangalliat
valeriangalliat force-pushed the patch/probe-untraced-media-src branch from 193db26 to b2d2e53 Compare November 10, 2026 00:52
@valeriangalliat valeriangalliat changed the title Fork patch: Probe untraced media src writes and changed data-var-src media Fork patch: Probe untraced media src writes Nov 10, 2026
@valeriangalliat
valeriangalliat force-pushed the patch/probe-untraced-media-src branch from b2d2e53 to 647ff8c Compare November 10, 2026 18:00
@valeriangalliat

Copy link
Copy Markdown
Collaborator Author

🤖 AI agent: sync/2026-11-10: rebased onto upstream cda84222a (just past v0.9.30). Conflict in mediaSrcMutation.ts: upstream #5255 resolves write targets in their lexical scopes. The patch is re-applied on that parser. Upstream issue filed: heygen-com/hyperframes#7504.

1:  b2d2e5302 ! 1:  647ff8c70 fix(render): probe media src writes the parser cannot trace
    @@ packages/parsers/src/mediaSrcMutation.ts: import * as acorn from "acorn";
        operation: "src_assignment" | "set_attribute";
        raw: string;
      }
    -@@ packages/parsers/src/mediaSrcMutation.ts: function lookupSelector(node: any, bindings: ReadonlyMap<string, string>): strin
    +@@ packages/parsers/src/mediaSrcMutation.ts: function selectorFromDomLookup(
        return method === "getElementById" ? `#${value}` : value;
      }
      
    @@ packages/parsers/src/mediaSrcMutation.ts: function lookupSelector(node: any, bin
        try {
          const ast = parseProgram(script);
     @@ packages/parsers/src/mediaSrcMutation.ts: export function extractMediaSrcMutations(script: string): MediaSrcMutation[] {
    -     acornWalk.simple(ast, {
    -       AssignmentExpression(node: any) {
    -         if (node.operator !== "=" || memberName(node.left) !== "src") return;
    --        const selector = lookupSelector(node.left.object, bindings);
    +           memberName(node.left) !== "src"
    +         )
    +           return;
    +-        const selector = lookupSelector(node.left.object, scopeAt(ancestors));
     -        if (!selector) return;
              mutations.push({
     -          selector,
    -+          selector: lookupSelector(node.left.object, bindings) ?? null,
    ++          selector: lookupSelector(node.left.object, scopeAt(ancestors)) ?? null,
                operation: "src_assignment",
                raw: script.slice(node.start, node.end),
              });
     @@ packages/parsers/src/mediaSrcMutation.ts: export function extractMediaSrcMutations(script: string): MediaSrcMutation[] {
    -       CallExpression(node: any) {
    -         if (memberName(node.callee) !== "setAttribute") return;
    -         if (literalString(node.arguments?.[0])?.toLowerCase() !== "src") return;
    --        const selector = lookupSelector(node.callee.object, bindings);
    +         if (node.callee.type !== "MemberExpression" || memberName(node.callee) !== "setAttribute")
    +           return;
    +         if (literalString(node.arguments[0])?.toLowerCase() !== "src") return;
    +-        const selector = lookupSelector(node.callee.object, scopeAt(ancestors));
     -        if (!selector) return;
              mutations.push({
     -          selector,
    -+          selector: lookupSelector(node.callee.object, bindings) ?? null,
    ++          selector: lookupSelector(node.callee.object, scopeAt(ancestors)) ?? null,
                operation: "set_attribute",
                raw: script.slice(node.start, node.end),
              });

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant