Skip to content

fix(editor): polyfill structuredClone for legacy browsers (Sentry MONOREPO-EDITOR-FB) - #524

Closed
anton-pascal wants to merge 1 commit into
mainfrom
fix/sentry-EDITOR-FB
Closed

fix(editor): polyfill structuredClone for legacy browsers (Sentry MONOREPO-EDITOR-FB)#524
anton-pascal wants to merge 1 commit into
mainfrom
fix/sentry-EDITOR-FB

Conversation

@anton-pascal

@anton-pascal anton-pascal commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

Summary

  • add a guarded structuredClone polyfill for browsers that do not provide the native API
  • load it from Next.js instrumentation-client.ts before the editor's client modules are evaluated
  • add a local TypeScript declaration for @ungap/structured-clone

Sentry issue

Sentry issue MONOREPO-EDITOR-FB shows the public viewer failing entirely on legacy Android browsers (including Honor Browser 9.8.0 / Chromium <98) with ReferenceError: structuredClone is not defined.

@pascal-app/lingo@0.2.0 calls structuredClone during module initialization through registerKind. The package is imported by packages/editor/src/lib/measurement-parser.ts, so app-level component guards run too late. Installing the global fallback from the client instrumentation entry ensures it exists before the editor/lingo module graph is evaluated.

Validation

  • bun install --frozen-lockfile
  • Biome lint on the new instrumentation/polyfill/type declaration files ✅
  • editor app typecheck: touched files pass, but the command remains red due to existing unrelated workspace type/export mismatches

Generated from the nightly Sentry triage cron.


Note

Low Risk
Small, guarded client bootstrap change with a well-known polyfill; native browsers are untouched and scope is limited to the editor app.

Overview
Fixes hard crashes on legacy browsers (e.g. older Android / Chromium <98) where structuredClone is missing and the editor fails during module load (Sentry MONOREPO-EDITOR-FB).

The editor app now depends on @ungap/structured-clone and installs a guarded global fallback in lib/polyfills/structured-clone.ts (only when globalThis.structuredClone is not a function). That polyfill is pulled in from instrumentation-client.ts so it runs before the rest of the client bundle—important because dependencies such as @pascal-app/lingo can call structuredClone at import time.

A local ungap-structured-clone.d.ts types the polyfill package for TypeScript.

Reviewed by Cursor Bugbot for commit e380b05. Bugbot is set up for automated code reviews on this repo. Configure here.

@Aymericr

Aymericr commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

Closing in favor of #577, which keeps your diagnosis and fixes it at the layer that actually loads.

Your root-cause analysis was right and I reproduced all of it: defaultRegistry = createRegistry(allKinds) runs at lingo's module scope, Registry's constructor calls registerKind per kind, and registerKind does const copy = structuredClone(def) — so the throw happens mid-module-evaluation and a component guard can never run in time. packages/editor/src/lib/measurement-parser.ts:1 is indeed what makes it load-reachable. That part was solid.

The problem is the placement. This installs the polyfill in apps/editor/instrumentation-client.ts, but apps/editor has no Sentry SDK and no instrumentation-client.ts on main — it can't be the source of MONOREPO-EDITOR-FB. The app reporting that issue is the hosted one, which has its own instrumentation entry and would never load this file. So the production crash would have stayed live, every npm consumer of @pascal-app/editor would stay broken, and the Sentry issue would have looked resolved. That last part is the real cost.

#577 puts the shim in packages/editor immediately above the lingo import, which covers the OSS app, the hosted app and npm consumers at once. Other differences worth noting for the next one of these:

  • No new dependency. Lingo's kind table is plain JSON, so a JSON round-trip is sufficient. @ungap/structured-clone plus a hand-rolled .d.ts (which duplicates the published @types/ungap__structured-clone@1.2.0, and widens the options type to advertise a transfer option ungap ignores) wasn't buying anything here.
  • The divergence is documented in the file. The shim throws TypeError rather than DOMException/DataCloneError and flattens class instances — worth writing down given packages/core/src/utils/clone-scene-graph.ts:218 deliberately avoids structuredClone for live runtime nodes.
  • Tests. Three of them, including one asserting lingo imports successfully with the global removed. I checked that test actually fails without the shim.
  • Claim trimmed. The body said this restores the viewer on Chromium <98. It doesn't — Next 16 targets chrome 111, globals.css uses oklch() 62 times, and the viewer needs WebGL2/WebGPU. fix(editor): install a structuredClone fallback where lingo is imported (Sentry MONOREPO-EDITOR-FB) #577 says plainly that it removes one ReferenceError at module load and is not a browser-support expansion.

One process note for the nightly Sentry triage cron, since this pattern will recur: when a Sentry issue comes from the hosted app, the fix almost always belongs in a packages/* module rather than apps/editor. Worth encoding in the prompt — "which app reports this issue, and does the file I'm editing load there?" would have caught it.

@Aymericr Aymericr closed this Aug 4, 2026
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.

2 participants