Fix front component crash on unknown elements (#22455)

## What

Front components are third-party React components rendered on the host
via remote-dom against an allow-list of elements. Today the host
renderer throws on any element tag it has no component for (e.g. a raw
tag produced by `innerHTML`), and there is no error boundary, so a
single unknown element crashes the whole widget.

This wraps the component registry with a fallback:
- a raw tag that has an allow-listed `html-*` equivalent is routed to
that safe wrapper (so a raw `iframe` renders through the existing
sandbox-forcing renderer instead of being dropped),
- tags with no safe renderer (`script`, `object`, `embed`, `link`,
`meta`, `base`, `noscript`, `style`) render nothing,
- any other unknown tag renders children only.

`RemoteRootRenderer` is also wrapped in an error boundary that fails
closed to the existing error panel, so a render error can no longer take
down the host.

## Notes

The host allow-list remains the single rendering gate. This is the first
hardening step of a broader effort to widen the DOM/Web API surface
available to front components; it is self-contained and does not change
behavior for components that only use allow-listed elements.

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22455?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
This commit is contained in:
Raphaël Bosi
2026-07-02 15:13:50 +02:00
committed by GitHub
parent 11b2990dd6
commit ff6a0c6e69
6 changed files with 143 additions and 4 deletions
@@ -12,11 +12,16 @@ import {
RemoteRootRenderer,
} from '@remote-dom/react/host';
import { useMemo, useState } from 'react';
import { ErrorBoundary } from 'react-error-boundary';
import { isDefined } from 'twenty-shared/utils';
import { ThemeProvider } from 'twenty-ui/theme-constants';
import { FrontComponentWorkerEffect } from '../../remote/components/FrontComponentWorkerEffect';
import { componentRegistry } from '../generated/host-component-registry';
import { createFallbackComponentRegistry } from '../utils/createFallbackComponentRegistry';
const fallbackComponentRegistry =
createFallbackComponentRegistry(componentRegistry);
type FrontComponentContentProps = {
componentUrl: string;
@@ -128,10 +133,17 @@ export const FrontComponentRenderer = ({
{isDefined(receiver) && isExecutionContextInitialized && (
<ThemeProvider colorScheme={colorScheme}>
<RemoteRootRenderer
receiver={receiver}
components={componentRegistry}
/>
<ErrorBoundary
onError={setError}
onReset={() => setError(null)}
resetKeys={[componentUrl]}
fallbackRender={() => null}
>
<RemoteRootRenderer
receiver={receiver}
components={fallbackComponentRegistry}
/>
</ErrorBoundary>
</ThemeProvider>
)}
</>