## What — TDD coverage layer, no fixes Renders every twenty-ui component export inside the real front-component sandbox (opaque-origin iframe + remote-dom worker, real SDK esbuild pipeline) through the existing Playwright-backed storybook vitest suite, and expresses **every sandbox gap as a golden known-failure test**. This PR deliberately ships **zero fixes** and is **fully green (267/267)**: each known issue has a test asserting the exact current broken behavior. A golden test fails on regression (an unexpected component starts failing) AND on fix (the documented failure disappears) — so every future fix must flip its golden assertion to the strict one, whose acceptance criteria are spelled out in each story's comment. - One gallery fixture per submodule under `src/__stories__/twenty-ui-gallery/` (~150 components) - `component-gallery.tsx`: each component renders inside its own error boundary; failures are aggregated with component name and error message (`data-failed-messages`) so one crash cannot mask the rest - Behavior-level stories that a fake fix cannot pass (MutationObserver must actually fire; a link click must reach `hostApi.navigate`) - `vitest.config.ts` re-export so the storybook in-UI "Run tests" button works ## Current state: 267/267 green — mergeable; 18 golden tests encode the catalog below ## Failure catalog (each entry = golden tests asserting today's broken behavior) ### 1. `MutationObserver.observe is not a function` — 6 failing tests `@remote-dom/polyfill` ships `MutationObserver` as an empty class: construction succeeds, the first `.observe()` call crashes. | Failing test | twenty-ui export(s) | Call site | |---|---|---| | Input React + Preact | `Radio`, `RadioGroup`, `CardPicker` | `@base-ui/react` field internals observe form state | | Surfaces React + Preact | `AppTooltip` | twenty-ui observes `document.body` to track anchors | | Worker Platform APIs: Mutation Observer React + Preact | (behavior test) | asserts the observer actually fires on a React-driven insertion — a no-op stub cannot pass it | Validated fix (branch history, commit 94b96d01e79e): implement real mutation semantics **locally in the worker** by tapping the same `@remote-dom/polyfill` hooks (`insertChild`/`removeChild`/`setAttribute`/`removeAttribute`/`setText`) remote-dom already uses to mirror mutations to the host. The worker tree is the source of truth — no host bridge needed. ### 2. `getComputedStyle is not a function` — 4 failing tests The remote-dom `Window` polyfill does not implement `getComputedStyle`; `@base-ui/react` Collapsible calls it on open. | Failing test | twenty-ui export(s) | |---|---| | Layout React + Preact | `AnimatedEaseInOut`, `AnimatedExpandableContainer` | | Json Visualizer React + Preact | `JsonTree`, `JsonArrayNode`, `JsonObjectNode`, `JsonNestedNode` (all via `JsonNestedNode`'s Collapsible) | Validated fix (commit 7af577054fd9): `getComputedStyle` is synchronous and layout only exists host-side, so a bridge is impossible — an inert-but-valid stub (`0s` durations, `none` animation names) is the honest terminal state. ### 3. No router context in the sandbox — 5 failing tests react-router's `Link` reads `NavigationContext`, which has no provider inside the worker: `Cannot destructure property 'basename' of 'useContext(...)' as it is null`. | Failing test | twenty-ui export(s) | |---|---| | Navigation React + Preact | `RawLink`, `UndecoratedLink` | | Data Display React + Preact | `LinkChip` | | HostApi: Router Link | acceptance test — the link must render AND its click must reach `hostApi.navigate` | Validated fix (commit 221c9c353dc8): SDK build plugin wraps the component tree in a low-level react-router `<Router>` whose custom navigator forwards `push`/`replace` to the SDK `navigate` host API (NOT a MemoryRouter, which would render links but swallow clicks into in-memory history). Falls back to a pass-through provider when the app has no react-router-dom dependency. **Security finding bundled in that commit:** clicking a mirrored `<a>` performs a native host-page navigation before the async worker round-trip can `preventDefault` — a front component can escape the sandbox by rendering a link. Fix: apply the renderer's existing preventDefault-then-forward guard (already used for form submits) to anchor clicks, keeping `target="_blank"` native. The Router Link story's flip-to acceptance assertions (click must reach `hostApi.navigate`) will hard-crash the vitest browser the moment links render without this guard — by design. ### 4. Open `Modal` hangs the React runtime — 1 failing test base-ui Dialog portal with `isOpen` never commits under the React runtime (no error thrown); works under Preact. | Failing test | twenty-ui export | |---|---| | Modal Open React (Modal Open Preact passes) | `Modal` | Isolated in its own fixture so the hang cannot mask the rest of the surfaces gallery (which keeps a closed Modal for mount coverage). Root cause not yet identified — first experiment: portal into a worker-owned container instead of the polyfilled `document.body`. ### 5. monaco cannot load in the sandbox — 2 failing tests `@monaco-editor/react` lazy-loads monaco via script injection, impossible in the polyfilled worker DOM (opaque-origin CSP, no script loading). The CodeEditor wrapper mounts; monaco's `onMount` never fires. | Failing test | twenty-ui export | |---|---| | Code Editor React + Preact | `CodeEditor` | Probably wont-fix in the worker: if front components need a code editor, the path is a host-rendered privileged component. The failing test is the documentation. ### Also worth knowing (no failing test possible yet) - `ResizeObserver` / `IntersectionObserver` / `matchMedia` are absent in the worker: components mount without them today only because nothing crashes at mount — behavior like popup auto-resize and `useIsMobile` is silently wrong. Real fix is a host bridge (async observers on the component's own mirrored elements only — never the host document). Behavior tests should land with that fix. - `Icon`/`IconsProvider`/`useIcons` are excluded from galleries by choice: they dynamic-import the multi-MB Tabler catalog; direct icon imports are the supported pattern in bundled front components. ## Iteration plan Each fix is one commit + one set of stories flipping green, in suggested order: 1. Worker-local MutationObserver (6 tests) — cherry-pick base: 94b96d01e79e 2. `getComputedStyle` + observer/matchMedia stubs (4 tests) — cherry-pick base: 7af577054fd9 3. Router provider + anchor click guard (5 tests) — cherry-pick base: 221c9c353dc8 4. Open-Modal React hang investigation (1 test) 5. CodeEditor: decide wont-fix + keep the failing story or convert to a documented skip (2 tests) ## How to run ``` npx nx run twenty-front-component-renderer:storybook:prebuild cd packages/twenty-front-component-renderer npx vitest run --config vitest.storybook.config.ts --project storybook ```
The #1 Open-Source CRM
Website ·
Documentation ·
Roadmap ·
Discord ·
Figma
Why Twenty
Twenty gives technical teams the building blocks for a custom CRM that meets complex business needs and quickly adapts as the business evolves. Twenty is the CRM you build, ship, and version like the rest of your stack.
Learn more about why we built Twenty
Installation
Cloud
The fastest way to get started. Sign up at twenty.com and spin up a workspace in under a minute, with no infrastructure to manage and always up to date.
Build an app
Scaffold a new app with the Twenty CLI:
npx create-twenty-app my-app
Define objects, fields, and views as code:
import { defineObject, FieldType } from 'twenty-sdk/define';
export default defineObject({
nameSingular: 'deal',
namePlural: 'deals',
labelSingular: 'Deal',
labelPlural: 'Deals',
fields: [
{ name: 'name', label: 'Name', type: FieldType.TEXT },
{ name: 'amount', label: 'Amount', type: FieldType.CURRENCY },
{ name: 'closeDate', label: 'Close Date', type: FieldType.DATE_TIME },
],
});
Then ship it to your workspace:
npx twenty app:publish --private
See the app development guide for objects, views, agents, and logic functions.
Self-hosting
Run Twenty on your own infrastructure with Docker Compose, or contribute locally via the local setup guide.
Everything you need
Twenty gives you the building blocks of a modern CRM (objects, views, workflows, and agents) and lets you extend them as code. Here's a tour of what's in the box.
Want to go deeper? Read the User Guide for product walkthroughs, or the
Documentation for developer reference.
|
|
|
|
|
|
Stack
TypeScript
Nx
NestJS, with BullMQ,
PostgreSQL,
Redis
React, with Jotai, Linaria and Lingui
Thanks
Thanks to these amazing services that we use and recommend for code review (Greptile), catching bugs (Sentry) and translating (Crowdin).
Join the Community
Star the repo ·
Discord ·
Feature requests ·
Releases ·
X ·
LinkedIn ·
Crowdin ·
Contribute





