Skip to content

Stop Chrome page translation from crashing React, and fix the None title sentinel - #783

Merged
InfinityBowman merged 2 commits into
mainfrom
fix/select-translate-and-none-title
Sep 13, 2026
Merged

InfinityBowman merged 2 commits into
mainfrom
fix/select-translate-and-none-title

Conversation

@InfinityBowman

Copy link
Copy Markdown
Owner

Why

A new user from Brazil hit the error boundary on complete-profile today (Sentry issue 7696361994). The replay shows the page was machine-translated by Chrome: the html element carries translated-ltr, and hundreds of <font> wrappers replaced text nodes. Radix Select then removed the placeholder text node from the trigger, React threw NotFoundError: Failed to execute 'removeChild' on 'Node', and the page went down. A second user (es-MX) hit the same click on Aug 27 and the same error three times on the checklist page the next day.

While reading the route I also found that the Title dropdown's "None" option used a __none sentinel that was never mapped back, so seven prod users had the literal string saved as their title.

What

  • Select: the shared SelectValue wraps a string placeholder in a span, and SelectItem wraps its children in a span before ItemText. React removes the span instead of a bare text node, so translation keeps working with no stale text.
  • Translation guard: installTranslationGuard() runs in the client entry and patches removeChild / insertBefore on Node.prototype to skip the call when the node is no longer a child of the expected parent. This is the standard workaround from Make React resilient to DOM mutations from Google Translate react/react#11538. Untranslated pages are unaffected, since that branch is the case the DOM would already have thrown on. A translated page can show stale text until the next render instead of crashing. It logs client.translation_guard once per page load (operation, translated flag, page lang) so Loki shows how often this happens.
  • complete-profile: maps the None sentinel back to an empty title, matching what the settings ProfileForm already did.

Data

The seven __none titles in prod were set to NULL directly (ids snapshotted first, 7 changes, 0 remaining).

Verification

  • New jsdom test for the guard: skipped removal and insertion on a replaced text node, normal children still handled, warning emitted once.
  • pnpm typecheck, pnpm lint, Prettier clean; web unit suite passes.

https://claude.ai/code/session_0127k6bNMdQoMLtEfnAQdAyV

…le sentinel

Chrome page translation swaps bare text nodes for font wrappers, and React then
crashes removing text it no longer owns. Two onboarding users hit this picking
a title. The placeholder and the portaled item label now sit inside spans.

The complete-profile Title select used a sentinel for None and never mapped it
back, so seven users had the literal sentinel saved as their title.

Claude-Session: https://claude.ai/code/session_0127k6bNMdQoMLtEfnAQdAyV
Chrome translate replaces bare text nodes with font wrappers, so React's
removeChild and insertBefore throw NotFoundError on the next render of that
text and the error boundary takes the page down. The guard skips those calls
when the node is no longer where React left it, leaving at worst stale
translated text until the next render. It logs once per page load so Loki
shows how often translated pages hit it.

Claude-Session: https://claude.ai/code/session_0127k6bNMdQoMLtEfnAQdAyV
@coderabbitai

coderabbitai Bot commented Sep 13, 2026

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 45 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: 5139a75f-8ee3-4ebd-a50f-24dc33469d1f

📥 Commits

Reviewing files that changed from the base of the PR and between 87e6f23 and d262b7e.

📒 Files selected for processing (5)
  • packages/web/src/client.tsx
  • packages/web/src/components/ui/select.tsx
  • packages/web/src/lib/__tests__/translationGuard.test.ts
  • packages/web/src/lib/translationGuard.ts
  • packages/web/src/routes/_auth/complete-profile.tsx

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@InfinityBowman
InfinityBowman merged commit 559502f into main Sep 13, 2026
10 checks passed
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