Skip to content

MutationObserver in createOverlayController has no attributeFilter, cascades on per-frame style writes #3402

@bginbey

Description

@bginbey

Summary

createOverlayController configures a MutationObserver on document.body with attributes: true and no attributeFilter, so the handler fires on every attribute mutation in the body subtree — style, class, id, aria-*, data-anything — even though the only attribute the library reads is data-sanity. Any per-frame attribute writer (GSAP, anime.js, framer-motion's style mode, hand-rolled rAF) anywhere in the page cascades reducer dispatches at frame rate, which can blow React's render-depth limit when an unrelated state update lands on top.

Source

src/ui/shared-state/SharedStateContext.ts, compiled in 3.2.4 to dist/_chunks-es/SharedStateContext.js and dist/_chunks-cjs/SharedStateContext.cjs:

```js
mo = new MutationObserver(handleMutation)
mo.observe(document.body, {
attributes: true, // ← over-collection
characterData: true,
childList: true,
subtree: true,
})
```

Proposed fix

Add attributeFilter: ['data-sanity']. The library's only consumer of attribute mutations is the data-sanity parser; nothing else in handleMutation's downstream path reads any other attribute, so this is a strict narrowing of what the observer sees with no behavior change for editors.

```diff
mo.observe(document.body, {
attributes: true,

  • attributeFilter: ['data-sanity'],
    characterData: true,
    childList: true,
    subtree: true,
    })
    ```

Repro

Any GSAP-animated element with a few data-sanity descendants will surface the cascade. Minimal recipe:

  1. Sanity Presentation iframe with Visual Editing enabled.
  2. A section that renders ~15 elements with data-sanity attributes inside a parent that GSAP animates per frame (e.g. `gsap.to(parent, { x: '-50%', repeat: -1, duration: 35 })`).
  3. Trigger any unrelated state update on top of the running animation (route change, theme switch, perspective toggle).
  4. React surfaces `Maximum update depth exceeded` from `Overlays.tsx:162` reducer dispatch.

Stack trace pattern: `MutationObserver.handleMutation` → `parseElements` → `updateElement` → `Overlays.tsx:162` reducer dispatch.

Why this matters

The library's docs encourage rich animations on content-driven pages, and the standard tools for those animations write to `style` attributes per frame. The current observer config makes Visual Editing incompatible with any of them in practice unless authors hand-suppress mutations subtree-by-subtree.

Workaround

Patching the dist chunks locally with pnpm patch / patch-package works fine. Happy to send a PR with the one-line fix if useful.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions