0013 — Tolerate DOM rewritten by browser page translation
Status: Accepted · Date: 2026-09-25
Context
Browser page translation rewrites the page's DOM. Safari (macOS and iOS) and
Chrome both replace the text nodes React rendered with translated copies. React
still holds the original nodes. On its next structural update it calls
removeChild on a node that is no longer in that parent, or insertBefore with
such a node as the reference. The DOM throws NotFoundError ("The object can
not be found here." in WebKit) in the middle of React's commit.
An error boundary cannot recover from this. The route boundary (app/error.tsx)
replaces the whole screen. After "Try again", the next update crashes the same
way, so the screen cannot be used at all while translation is on.
This has happened twice:
- 2026-08-18 (00a9f317): the session drawer's Performance tab. Fixed per
site: captions render as one text node (
captionLine), measured values carrytranslate="no"(FitValue, the performance charts), and the tab got its own boundary (session-tab-boundary.tsx). - 2026-09-20: a Portuguese-speaking coach used the English portal through
Safari's translation. Every time range change on the dashboard crashed the page
(
removeChild→NotFoundError, boundarysegment), so the coach could not get past "Last week". The data requests had succeeded.
Fixing individual sites does not scale. Almost any conditional content inside translated text can trigger the crash, and Safari rewrites more of the DOM than Chrome does.
Decision
Both apps patch Node.prototype.removeChild and Node.prototype.insertBefore
at startup to tolerate a node that something other than the app has moved.
This is the workaround from facebook/react#11538.
installForeignDomGuard (@enode/core/diagnostics/foreign-dom-guard) is called
from each app's ErrorEngineInit, next to installGlobalErrorHandlers.
removeChildof a node that is not a child of the parent does nothing. The node has already left that parent.insertBeforewith a reference that is not a child of the parent appends the new node. The content stays visible instead of silently missing.- Calls against real children are unchanged.
- The first guarded call of each kind leaves an
errorbreadcrumb, so a support report shows that the page had been rewritten.
Alternatives rejected
translate="no"on the whole document. This prevents the crash but removes translation for users whose language the apps do not offer yet.- Only per-site hardening. Every new screen would repeat the risk, and this is already the second incident.
Consequences
- Translated pages no longer crash. They can drift instead: text the translator replaced no longer receives React's updates and can show a stale value until the element is re-created. An appended node can appear out of order within the one parent that was rewritten.
- A genuine app bug that removes a node from the wrong parent no longer throws. It leaves the breadcrumb instead.
- The per-site measures from 00a9f317 stay. They keep the content correct under translation, while the guard only keeps the page from crashing.