Skip to content

Commit dc3af1c

Browse files
awkoyclaude
andauthored
[OPIK-7435] [FE] Fix Google Translate removeChild crash on onboarding email step (#7562)
* [OPIK-7435] [FE] Fix Google Translate removeChild crash on onboarding email step The mobile ConnectStep submit button renders a state-dependent label (Email setup instructions -> Sending... -> Instructions sent!) as a bare text sibling of the button icon. Under browser translation Google Translate wraps that text node in a <font> element; submitting the email reconciles the button (icon + label swap) against a re-parented node and throws "NotFoundError: Failed to execute 'removeChild' on 'Node'". Wrap the label in a <span> so React swaps a stable wrapper element instead of the bare text node; the label stays fully translatable. See react/react#11538. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * [OPIK-7435] [FE] Add frontend rule: browser-translation-safe dynamic text Document the Google Translate removeChild crash class in the opik-frontend skill (Critical Gotchas) so new dynamic/animated text wraps interpolated strings in an element instead of rendering bare text nodes. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 6c61906 commit dc3af1c

2 files changed

Lines changed: 24 additions & 5 deletions

File tree

.agents/skills/opik-frontend/SKILL.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,16 @@ const selectedEntity = useEntityStore(state => state.selectedEntity);
5252
const { selectedEntity, filters } = useEntityStore();
5353
```
5454

55+
### Browser Translation Safety (Google Translate)
56+
Many users auto-translate the page; the translator wraps text nodes in `<font>` elements, so React throws `NotFoundError: removeChild` when it reconciles a **bare dynamic text node** it re-parented. Wrap dynamic/conditional strings in their own element instead of rendering bare text.
57+
```typescript
58+
// ❌ bare dynamic text → crash under translation
59+
<button>{icon}{label}</button>
60+
// ✅ wrap it → React swaps a stable element, stays translatable
61+
<button>{icon}<span>{label}</span></button>
62+
```
63+
For timer-driven text (typewriter/counter), also avoid per-tick `setState` — write into a ref'd node's `textContent` (React never reconciles it), or mark a decorative node `translate="no"`. Ref: [facebook/react#11538](https://github.com/facebook/react/issues/11538) (OPIK-7428, OPIK-7435).
64+
5565
## Layer Architecture
5666

5767
### Shared layers (used by all versions)

apps/opik-frontend/src/v2/pages/GetStartedPage/MobileOnboarding/ConnectStep.tsx

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -161,11 +161,20 @@ const ConnectStep: React.FC<ConnectStepProps> = ({ userEmail = "" }) => {
161161
) : (
162162
<Mail className="size-3.5" />
163163
)}
164-
{isPending
165-
? "Sending..."
166-
: emailSent
167-
? "Instructions sent!"
168-
: "Email setup instructions"}
164+
{/* Wrap the state-dependent label in a <span> so React swaps a
165+
stable wrapper element instead of a bare text node. Under
166+
browser translation Google Translate re-parents text nodes
167+
into <font> elements; reconciling this label (icon + text swap
168+
on submit) against a re-parented bare text node throws
169+
"NotFoundError: removeChild". The wrapper keeps the label fully
170+
translatable (unlike translate="no"). See facebook/react#11538. */}
171+
<span>
172+
{isPending
173+
? "Sending..."
174+
: emailSent
175+
? "Instructions sent!"
176+
: "Email setup instructions"}
177+
</span>
169178
</Button>
170179
</form>
171180

0 commit comments

Comments
 (0)