Document current front component limitations (#23549)
Front components are still under active development, but the docs did not say so, and two of the three field reports we got on Discord were misdiagnosed because the sandbox fails silently. Adds a "Current limitations" section to the front components page covering layout measurement, DOM access, events, CSS scoping, storage and network, with the workaround for each. Also corrects the testing page, which claimed front components get "browser APIs" when the sandbox only implements a partial DOM. Every limitation was checked against the code rather than copied from the roadmap, which turned up a few stale entries: CSS imports work, `aria-*`/`data-*` now cross, and `MutationObserver` throws on `.observe()` rather than silently never firing. <!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/23549?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:
@@ -6,6 +6,10 @@ icon: "window-maximize"
|
||||
|
||||
Front components are React components that render directly inside Twenty's UI. They run in an **isolated Web Worker** using Remote DOM — your code executes inside a sandboxed, opaque-origin iframe, yet its UI still renders natively in the page rather than being confined to that iframe.
|
||||
|
||||
<Warning>
|
||||
Front components are still under active development. Your code runs against a partial DOM, not a real browser page, so advanced usages can fail, often silently. See [Current limitations](#current-limitations).
|
||||
</Warning>
|
||||
|
||||
## Where front components can be used
|
||||
|
||||
Front components can render in three locations within Twenty:
|
||||
@@ -696,3 +700,72 @@ const Card = () => {
|
||||
Because `useTheme()` is a hook, you read tokens inside the component body, so the values always reflect the live theme. The same token map is also exported as the `themeCssVariables` constant, but prefer `useTheme()` in front components — a module-level constant that dereferences `themeCssVariables` can be undefined while the app manifest is extracted.
|
||||
|
||||
To branch on the active scheme explicitly, read it with `useColorScheme()` from `twenty-sdk/front-component`, which returns `'light'` or `'dark'`.
|
||||
|
||||
## Current limitations
|
||||
|
||||
Front components are under active development. Rendering, styling and handling events works well. Anything that reaches *past* rendering (measuring an element, calling a DOM method on a ref, portaling outside your tree, touching browser storage) is missing or incomplete today, and most of it fails silently: no exception, and no TypeScript error either, since the scaffold is typed against the full browser DOM.
|
||||
|
||||
If one of these blocks you, [open an issue](https://github.com/twentyhq/twenty/issues/new/choose) so it gets prioritized.
|
||||
|
||||
### Layout and measurement
|
||||
|
||||
Nothing can measure itself yet.
|
||||
|
||||
| API | What happens |
|
||||
|-----|--------------|
|
||||
| `getBoundingClientRect()`, `getClientRects()` | Throws |
|
||||
| `offsetWidth`, `clientWidth`, `scrollWidth`, `offsetTop`, ... | Silently `undefined`, so `width ?? 0` yields `0` and `width > 600` is always false |
|
||||
| `ResizeObserver`, `IntersectionObserver` | `ReferenceError` (`typeof` guards do work) |
|
||||
| `window.matchMedia()`, `window.getComputedStyle()` | Throws |
|
||||
| `window.innerWidth`, `innerHeight`, `devicePixelRatio` | Silently `undefined` |
|
||||
| `new MutationObserver(fn)` | Constructs, then `.observe()` throws |
|
||||
|
||||
So recharts `ResponsiveContainer`, Floating UI / Popper, list virtualization and drag-to-resize do not work yet. Do layout in CSS instead: your stylesheet reaches the real page, so flexbox, grid, `aspect-ratio`, `clamp()` and `@container` all behave normally.
|
||||
|
||||
<Note>
|
||||
`requestAnimationFrame`, `fetch`, `setTimeout` and `queueMicrotask` work without the `window.` prefix. Only `window.requestAnimationFrame(...)` and friends throw.
|
||||
</Note>
|
||||
|
||||
### DOM access
|
||||
|
||||
A `ref` gives you a sandbox element, not an `HTMLElement`.
|
||||
|
||||
| What you write | What happens | Use instead |
|
||||
|----------------|--------------|-------------|
|
||||
| `ref.current.focus()`, `.click()`, `.select()`, `.setSelectionRange()`, `.scrollIntoView()`, `video.play()` | Throws | Controlled components; read values from `event.target` |
|
||||
| `element.classList.add(...)` | Throws (`classList` is `undefined`) | Build the `className` string yourself |
|
||||
| `document.getElementById()`, `getElementsByClassName()`, `createTreeWalker()` | Throws | `querySelector()` / `querySelectorAll()`, which work |
|
||||
| `document.activeElement` | Always `undefined` | Track focus with `onFocus` / `onBlur` |
|
||||
| `<canvas>` | Renders nothing, no error | SVG, or draw offscreen and show an `<img src={dataUrl}>` |
|
||||
| `createPortal(node, document.body)` | Renders nothing, while `isConnected` reports success | Overlays inline with `position: absolute`, or pass the library your own container element |
|
||||
|
||||
The portal gap is why Radix, Headless UI, MUI and react-select popovers render nothing by default. Most accept a container prop; point it at an element you rendered.
|
||||
|
||||
### Events
|
||||
|
||||
Mouse, pointer, touch, drag, keyboard, focus, `input`/`change`/`submit`, `scroll`/`wheel`/`contextmenu` and `animationend`/`transitionend` cross to the host, plus a few per element: `load`/`error` on `<img>`, clipboard and composition on `<input>`/`<textarea>`, media on `<video>`/`<audio>`, `toggle` on `<details>`/`<dialog>`. Anything else (`onAuxClick`, `onSelect`, `onInvalid`, `onReset`, `onAnimationStart`, pointer capture, `onLoad` off `<img>`) is dropped without warning.
|
||||
|
||||
`document.addEventListener()` and `window.addEventListener()` register without error and never fire, which is why a drag stops as soon as the pointer leaves the element it started on. `event.preventDefault()` does not cross either; form submission, `dragover`/`drop` and link clicks are already guarded for you.
|
||||
|
||||
### Attributes and styling
|
||||
|
||||
Each element forwards its own properties to the host DOM (`href` on `<a>`, `src`/`alt` on `<img>`, `value`/`placeholder`/`disabled` on `<input>`, and so on), plus a common set on every element: `id`, `className`, `style`, `title`, `tabIndex`, `role`, `draggable` and any `aria-*` / `data-*` attribute (hyphenated, so `ariaLabel` is dropped). Anything outside that is silently discarded, so express custom state as `data-*`.
|
||||
|
||||
Component CSS, whether from `import './styles.css'`, CSS-in-JS or a `<style>` element, is injected into the host page's `<head>` **unscoped**. So class names collide with Twenty's own (prefix them, and never write bare `div { ... }` selectors), and `@media` matches the browser window rather than your widget (use `@container` with your own `container-type`). Inline `style` props are unaffected.
|
||||
|
||||
### Storage and network
|
||||
|
||||
`localStorage`, `sessionStorage`, IndexedDB, cookies, the Cache API and `BroadcastChannel` are all unavailable, since the component runs in a worker at an opaque origin. To persist state, call a [logic function](/developers/extend/apps/logic/logic-functions) and use its [key-value store](/developers/extend/apps/logic/key-value-store).
|
||||
|
||||
`fetch` works, with caveats:
|
||||
|
||||
- Calls to the Twenty API and your app's routes are proxied by the host, so prefer [`RestApiClient`](#calling-the-twenty-rest-api). On proxied calls, `AbortSignal` and the other `RequestInit` options are dropped, and only `string` and `URLSearchParams` bodies are supported.
|
||||
- Other origins leave the sandbox with `Origin: null`, so a third-party API answers only if it sends `Access-Control-Allow-Origin: *`. Call it from a logic function instead.
|
||||
- `fetch('/rest/people')` is never matched to the Twenty API, because the sandbox has no page URL to resolve a relative path against.
|
||||
|
||||
### Other gaps
|
||||
|
||||
- **File contents.** `<input type="file">` gives your handler file metadata only, not the bytes, so `FileReader` and uploads are not possible yet.
|
||||
- **Drag-and-drop payloads.** Drag events fire, but `event.dataTransfer` is `undefined`.
|
||||
- **Node built-ins.** `fs`, `path` and `node:crypto` fail the build, so move that work into a [logic function](/developers/extend/apps/logic/logic-functions). Web Crypto, `fetch`, `TextEncoder` and `URL` are available.
|
||||
- **`<iframe>`** is always re-sandboxed without `allow-same-origin`, so an embed relying on its own session renders logged out. It has no `onLoad` either.
|
||||
|
||||
@@ -55,3 +55,5 @@ A Twenty app's **layout layer** is everything the user sees: where the app surfa
|
||||
| **Command menu (Cmd+K)** | A pinned quick action or hidden command | `defineCommandMenuItem` |
|
||||
|
||||
Front components run inside an isolated Web Worker using Remote DOM — they render *natively* in the page (not inside an iframe), but cannot reach the host page or DOM directly. Communication with Twenty happens through a message-passing host API.
|
||||
|
||||
They are still under active development: the sandbox implements a partial DOM, so advanced usages can fail. See [Current limitations](/developers/extend/apps/layout/front-components#current-limitations).
|
||||
|
||||
@@ -60,7 +60,7 @@ The build step uses esbuild to produce a single self-contained file per logic fu
|
||||
|
||||
**Logic functions** run in a Node.js environment. Node built-in modules (`fs`, `path`, `crypto`, `http`, etc.) are available and do not need to be installed.
|
||||
|
||||
**Front components** run in a Web Worker. Node built-in modules are **not** available — only browser APIs and npm packages that work in a browser environment.
|
||||
**Front components** run in a Web Worker. Node built-in modules are **not** available — only npm packages that work in a browser environment. Note that the sandbox implements a *partial* DOM, so a package can build cleanly and still fail at runtime; see [Current limitations](/developers/extend/apps/layout/front-components#current-limitations).
|
||||
|
||||
Both environments have `twenty-client-sdk/core` and `twenty-client-sdk/metadata` available as pre-provided modules — these are not bundled but resolved at runtime by the server.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user