[OPIK-7428] [FE] Prevent Google Translate removeChild crash on mobile get-started#7556
Conversation
… get-started The mobile WelcomeStep typewriter (TypingText) rewrites a text node every 30-80ms. When the browser auto-translates the page, Google Translate rewrites the DOM (wrapping text nodes in <font> elements), and React's next commit then throws "NotFoundError: Failed to execute 'removeChild' on 'Node'". The continuous animation makes the crash near-certain once translation is active. Mark the animated demo prompt translate="no" so the browser excludes it from translation (Google's recommended handling for JS-updated dynamic content). Translate no longer mutates that subtree, so React never hits the crash. The animation is unchanged and the rest of the page still translates. OPIK-7428 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
⏱️ pre-commit per-hook timing
⏭️ 38 skipped (no matching files changed)
|
| still translates. */} | ||
| <div | ||
| className="min-h-5 text-sm leading-5 text-foreground" | ||
| translate="no" |
There was a problem hiding this comment.
💡 suggestion | Testing
Nice, tightly-scoped fix — and I was able to fully verify it. I reproduced the exact NotFoundError: removeChild crash on main in a mobile Chrome viewport by racing the live typewriter against a faithful Google-Translate DOM mutation (wrapping the animated text node in <font>), and confirmed the crash disappears with this translate="no" in place while the animation keeps running. ✅
Since the whole fix hinges on this one attribute staying put, here's an optional regression test that fails without it and passes with it (deterministic, no flakiness). Feel free to add it as WelcomeStep.test.tsx if you like — no pressure:
import { describe, it, expect } from "vitest";
import { render } from "@testing-library/react";
import WelcomeStep from "./WelcomeStep";
describe("WelcomeStep", () => {
// Regression guard for OPIK-7428: the typewriter rewrites this text node
// every few dozen ms. If the browser is allowed to translate it, Google
// Translate re-parents the node (wrapping it in <font>) and React's next
// commit crashes with "NotFoundError: removeChild". translate="no" excludes
// just this animated node, which prevents the crash.
it("marks the animated demo prompt as non-translatable", () => {
const { container } = render(<WelcomeStep />);
const noTranslate = container.querySelector('[translate="no"]');
expect(noTranslate).not.toBeNull();
// The caret span lives inside the animated node, so its non-translatable
// ancestor is the exact subtree that must be excluded.
expect(noTranslate?.querySelector(".animate-caret-blink")).not.toBeNull();
});
});🤖 Review posted via /review-github-pr
JetoPistola
left a comment
There was a problem hiding this comment.
✅ Approved — verified end-to-end.
I reproduced the reported NotFoundError: removeChild crash on main (mobile Chrome viewport, live typewriter raced against a faithful Google-Translate DOM mutation) and confirmed it's gone with this translate="no" fix while the animation keeps running. Root cause, fix, and scope all check out; the trade-off (one decorative demo prompt not translated) is well-documented.
Left one optional, non-blocking suggestion inline: a small regression test that guards the attribute.
🤖 Review posted via /review-github-pr
Details
Production console crash on the Opik get-started onboarding (mobile), reported from a Chrome/Android session in a non-English locale:
MobileOnboarding/WelcomeStep.tsxrenders a JS typewriter (TypingText) that rewrites a text node every 30–80 ms. When the browser auto-translates the page, Google Translate rewrites the DOM (wrapping text nodes in<font>elements). React's next commit then callsremoveChildon a node whose parent changed and throws — the continuous animation makes the crash near-certain once translation is active (see facebook/react#11538).Fix: mark the animated demo prompt
translate="no"so the browser excludes it from translation (Google's recommended handling for JS-updated dynamic content). Translate no longer mutates that subtree, so React never hits the crash. The typewriter animation is unchanged and the rest of the page still translates; the only trade-off is that this one decorative example prompt is not translated.Change checklist
Issues
OPIK-7428
AI-WATERMARK
AI-WATERMARK: yes
translate="no"fix, and local reproductionTesting
--max-warnings=0); frontend eslint + typecheck CI checks pass.Documentation
No documentation changes needed — the fix is a single
translate="no"attribute on the animated onboarding prompt.