Repository navigation
Conversation
Browser translators replace React-managed text nodes with <font> elements. React then calls removeChild/insertBefore with nodes that are no longer children of the parent, which throws NotFoundError, unmounts the app and shows the 500 error page (e.g. when clicking any Button that toggles isLoading). Patch Node.prototype.removeChild/insertBefore so only those would-be-throwing calls are handled gracefully, keeping translation usable. See react/react#11538.
514c225 to
d2fd25b
Compare
| if (child.parentNode !== this) { | ||
| return child; | ||
| } |
There was a problem hiding this comment.
When a translator replaces a conditional text node, removeChild returns without removing the visible <font> replacement. If a swarm node loses its manager status, the old status can remain on screen. The fallback needs to account for the translator’s visible node, not just the detached text node.
There was a problem hiding this comment.
Intentional trade-off. Translators detach the original text node and insert their own <font> without keeping any link back to it, so from removeChild there's no reliable way to tell which <font> replaced the node React is removing. Guessing could remove unrelated content.
The goal of this patch is to stop the app from crashing (currently the whole tree unmounts and users get the 500 page). Text that changes after translation can look stale until the next re-render, which is noted in the PR description as a known limitation. Fixing it properly means wrapping the affected conditional text in elements (e.g. <span>) component by component. That's out of scope here and can be done case by case if needed.
| if (child && child.parentNode !== this) { | ||
| // Appending keeps the new node visible and attached where React | ||
| // expects its parent to be, so later removals still work. | ||
| return originalInsertBefore.call(this, node, null) as T; |
There was a problem hiding this comment.
There was a problem hiding this comment.
Intentional. Once the translator has replaced the reference node, its original position is unknown, so inserting before it isn't possible. The two options are dropping the insertion (the spinner, or any new content, never shows up, and later inserts that use it as a reference fail the same way) or appending to the correct parent. Appending keeps the node visible and keeps React's view of the DOM consistent, so later removals work natively.
In practice, the visible effect is the spinner sitting on the other side of a translated button label while it's loading, which is cosmetic compared to the current crash. Untranslated pages are unaffected: the fallback only runs when the native call would have thrown.
There was a problem hiding this comment.
That’s fair. Once the translator has detached the reference node, its original position cannot be recovered reliably. Appending is the safer fallback because it keeps the inserted node visible and attached to the parent React expects, allowing subsequent removals to proceed; dropping the insertion could leave React’s DOM state inconsistent and reproduce the same failure on later updates. The reversed spinner position is therefore an accepted cosmetic limitation, while untranslated pages continue through the native implementation unchanged.
Tip: You can customize Greptile's behavior for this repo with .greptile/rules.md and .greptile/config.json.
What is this PR about?
When the dashboard is translated with Google Translate (or Edge's translator / translation extensions), interacting with the UI crashes the whole app and shows the 500 error page.
Root cause: translators replace React-managed text nodes with their own
<font>elements. React still holds references to the original (now detached) text nodes, so the nextinsertBefore/removeChildagainst them throwsNotFoundError. The error is uncaught, React unmounts the tree, and Next.js renders_error(nostatusCodeon the client → "500").The most common trigger is our
Buttoncomponent:{isLoading && <Loader2 />}is inserted before the (translated) label text, so clicking almost any button that shows a loading state crashes the page:Fix: apply the well-known workaround from react/react#11538.
Node.prototype.removeChild/insertBeforeare patched on the client (imported from_app.tsx) so that only the calls that would otherwise throw are handled gracefully:removeChild(child)wherechildis no longer a child → no-opinsertBefore(node, ref)whererefis no longer a child → append to the parent instead, so the new node is still shown and later removals still workAll other calls go through the native implementation unchanged, so behavior without a translator is identical. Translation keeps working instead of being disabled with
translate="no", which matters since the UI is English-only.Known limitation (inherent to translators mutating the DOM): text that React updates after translation may keep showing the stale translated text until the next full re-render — but the app no longer crashes.
How it was tested
Local Dokploy instance (dev mode,
canary+ this PR) — real Google Translate (Google Website Translator switched to Korean) driven by Playwright/Chromium. Each run started from a fresh database. "Before" is the same build with only the_app.tsximport removed./register→ click RegisterNotFoundError: Failed to execute 'insertBefore' on 'Node'in<LoaderCircle>, app tree unmounted (blank page)insertBeforeerror, blank page/dashboard/projects→ Create Project → CreateinsertBefore+removeChild(<Text>) errors, blank pageIn dev mode the crash shows up as a blank page; in a production build the same uncaught error is expected to render
_error, which displays 500 (nostatusCodeon the client) — the symptom users report.Minimal reproduction — React/React DOM
19.2.7in headless Chromium with the same pattern asButton({isLoading && <svg />}{children}), simulating the translator's<font>replacement: crashes without the patch, works with it, and produces identical DOM output to the unpatched build when the page is not translated.tsc --noEmitandbiome checkpass.Checklist
Before submitting this PR, please make sure that:
canarybranch.Issues related (if applicable)
N/A — refs react/react#11538
The PR appears safe to merge, though translated pages can show an old status or a misplaced loading icon.
Summary
This PR keeps the dashboard updating when a browser translator replaces text nodes that React later tries to change. It applies the fix app-wide while leaving ordinary DOM calls unchanged.
Reviews (1) · Last reviewed commit: 514c225