From 66d7182d026fc276feb77a54d11752f712a472db Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Rapha=C3=ABl=20Bosi?=
<71827178+bosiraphael@users.noreply.github.com>
Date: Tue, 3 Feb 2026 16:54:18 +0100
Subject: [PATCH] Remote dom twenty UI POC (#17652)
Create a proof of concept which allows us to use twenty-ui in the
remote-dom.
For now only the button is allowed.
https://github.com/user-attachments/assets/e5441d2c-eb63-4b99-931b-86ee14621393
Known limitation: The icons are not yet available
---
.../components/FrontComponentRenderer.tsx | 14 +++---
.../utils/mockFrontComponent.ts | 14 +++++-
packages/twenty-sdk/package.json | 1 +
.../remote-dom/generateRemoteDomElements.ts | 41 ++++++++++++++--
.../generators/host-registry.generator.ts | 47 ++++++++++++++++++-
.../scripts/remote-dom/generators/schemas.ts | 19 +++++++-
.../AllowedUiComponents.ts | 26 ++++++++++
.../src/front-component-constants/index.ts | 2 +
.../components/FrontComponentRenderer.tsx | 17 +++++--
.../host/generated/host-component-registry.ts | 8 ++++
.../twenty-sdk/src/front-component/index.ts | 3 ++
.../remote/generated/remote-components.ts | 29 ++++++++++++
.../remote/generated/remote-elements.ts | 27 +++++++++++
packages/twenty-ui/project.json | 2 +-
yarn.lock | 1 +
15 files changed, 230 insertions(+), 21 deletions(-)
create mode 100644 packages/twenty-sdk/src/front-component-constants/AllowedUiComponents.ts
diff --git a/packages/twenty-front/src/modules/front-components/components/FrontComponentRenderer.tsx b/packages/twenty-front/src/modules/front-components/components/FrontComponentRenderer.tsx
index 3683a34770..ff588847d9 100644
--- a/packages/twenty-front/src/modules/front-components/components/FrontComponentRenderer.tsx
+++ b/packages/twenty-front/src/modules/front-components/components/FrontComponentRenderer.tsx
@@ -1,12 +1,10 @@
-import { useState } from 'react';
-
-import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
-import { t } from '@lingui/core/macro';
-import { isDefined } from 'twenty-shared/utils';
-
import { getMockFrontComponentUrl } from '@/front-components/utils/mockFrontComponent';
+import { useSnackBar } from '@/ui/feedback/snack-bar-manager/hooks/useSnackBar';
+import { useTheme } from '@emotion/react';
+import { t } from '@lingui/core/macro';
+import { useState } from 'react';
import { FrontComponentRenderer as SharedFrontComponentRenderer } from 'twenty-sdk/front-component';
-
+import { isDefined } from 'twenty-shared/utils';
type FrontComponentRendererProps = {
frontComponentId: string;
};
@@ -14,6 +12,7 @@ type FrontComponentRendererProps = {
export const FrontComponentRenderer = ({
frontComponentId: _frontComponentId,
}: FrontComponentRendererProps) => {
+ const theme = useTheme();
const [hasError, setHasError] = useState(false);
const { enqueueErrorSnackBar } = useSnackBar();
@@ -35,6 +34,7 @@ export const FrontComponentRenderer = ({
return (
diff --git a/packages/twenty-front/src/modules/front-components/utils/mockFrontComponent.ts b/packages/twenty-front/src/modules/front-components/utils/mockFrontComponent.ts
index 7147f702c5..781e9b58ee 100644
--- a/packages/twenty-front/src/modules/front-components/utils/mockFrontComponent.ts
+++ b/packages/twenty-front/src/modules/front-components/utils/mockFrontComponent.ts
@@ -1,8 +1,18 @@
import { isDefined } from 'twenty-shared/utils';
-// eslint-disable-next-line
const mockFrontComponentCode = `
-var r=globalThis.React.useState,s=globalThis.React.useEffect;var n=globalThis.jsx,o=globalThis.jsxs,g=globalThis.React.Fragment;var e=globalThis.RemoteComponents,x=()=>{let[a,l]=r(0),[m,p]=r(0);return s(()=>{let t=setInterval(()=>{p(i=>i+1)},1e3);return()=>clearInterval(t)},[]),o(e.HtmlDiv,{style:{padding:"20px",fontFamily:"Arial, sans-serif",maxWidth:"400px",margin:"0 auto",display:"flex",flexDirection:"column",alignItems:"center"},children:[n(e.HtmlH1,{style:{color:"#333",marginBottom:"20px",fontSize:"24px"},children:"Test Component"}),o(e.HtmlP,{style:{fontSize:"18px",marginBottom:"10px",color:"#666"},children:["Count: ",a]}),o(e.HtmlP,{style:{fontSize:"18px",marginBottom:"20px",color:"#666"},children:["Timer: ",m,"s"]}),n(e.HtmlButton,{onClick:()=>l(a+1),style:{padding:"10px 20px",fontSize:"16px",backgroundColor:"#007bff",color:"white",border:"none",borderRadius:"5px",cursor:"pointer",transition:"all 0.3s ease",boxShadow:"0 2px 4px rgba(0, 0, 0, 0.2)"},onMouseEnter:t=>{t.currentTarget.style.backgroundColor="#0056b3",t.currentTarget.style.transform="translateY(-2px)",t.currentTarget.style.boxShadow="0 4px 8px rgba(0, 0, 0, 0.3)"},onMouseLeave:t=>{t.currentTarget.style.backgroundColor="#007bff",t.currentTarget.style.transform="translateY(0)",t.currentTarget.style.boxShadow="0 2px 4px rgba(0, 0, 0, 0.2)"},children:"Increment"})]})},b=globalThis.jsx(x,{});export{b as default};
+const { jsx } = globalThis;
+const { TwentyUiButton } = globalThis.RemoteComponents;
+
+export default jsx(TwentyUiButton, {
+ variant: 'primary',
+ disabled: false,
+ fullWidth: false,
+ onClick: () => {
+ console.log('Button clicked');
+ },
+ title: 'Click me',
+});
`;
let cachedMockBlobUrl: string | null = null;
diff --git a/packages/twenty-sdk/package.json b/packages/twenty-sdk/package.json
index 2a9d4975af..be7548f017 100644
--- a/packages/twenty-sdk/package.json
+++ b/packages/twenty-sdk/package.json
@@ -63,6 +63,7 @@
"jsonc-parser": "^3.2.0",
"lodash.camelcase": "^4.3.0",
"react": "^18.0.0",
+ "twenty-ui": "workspace:*",
"typescript": "^5.9.2",
"uuid": "^13.0.0",
"vite": "^7.0.0",
diff --git a/packages/twenty-sdk/scripts/remote-dom/generateRemoteDomElements.ts b/packages/twenty-sdk/scripts/remote-dom/generateRemoteDomElements.ts
index 823aac42ff..ebaa64215b 100644
--- a/packages/twenty-sdk/scripts/remote-dom/generateRemoteDomElements.ts
+++ b/packages/twenty-sdk/scripts/remote-dom/generateRemoteDomElements.ts
@@ -1,10 +1,10 @@
-/* eslint-disable no-console */
import * as prettier from '@prettier/sync';
import * as fs from 'fs';
import * as path from 'path';
import { IndentationText, Project, QuoteKind } from 'ts-morph';
import { ALLOWED_HTML_ELEMENTS } from '../../src/front-component-constants/AllowedHtmlElements';
+import { ALLOWED_UI_COMPONENTS } from '../../src/front-component-constants/AllowedUiComponents';
import { COMMON_HTML_EVENTS } from '../../src/front-component-constants/CommonHtmlEvents';
import { EVENT_TO_REACT } from '../../src/front-component-constants/EventToReact';
import { HTML_COMMON_PROPERTIES } from '../../src/front-component-constants/HtmlCommonProperties';
@@ -17,6 +17,7 @@ import {
generateRemoteElements,
HtmlElementConfigArrayZ,
OUTPUT_FILES,
+ UiComponentConfigArrayZ,
} from './generators';
const SCRIPT_DIR = path.dirname(new URL(import.meta.url).pathname);
@@ -59,6 +60,28 @@ const getHtmlElementSchemas = (): ComponentSchema[] => {
}));
};
+const getUiComponentSchemas = (): ComponentSchema[] => {
+ const result = UiComponentConfigArrayZ.safeParse(ALLOWED_UI_COMPONENTS);
+
+ if (!result.success) {
+ throw new Error(
+ `Invalid UI component configuration:\n${formatZodError(result.error)}`,
+ );
+ }
+
+ return result.data.map((component) => ({
+ name: component.name,
+ tagName: component.name,
+ customElementName: component.tag,
+ properties: component.properties,
+ events: COMMON_HTML_EVENTS,
+ isHtmlElement: false,
+ htmlTag: undefined,
+ componentImport: component.componentImport,
+ componentPath: component.componentPath,
+ }));
+};
+
const createProject = (): Project => {
return new Project({
manipulationSettings: {
@@ -99,8 +122,11 @@ const main = (): void => {
console.log('📖 Generating remote DOM elements...\n');
let htmlElements: ComponentSchema[];
+ let uiComponents: ComponentSchema[];
+
try {
htmlElements = getHtmlElementSchemas();
+ uiComponents = getUiComponentSchemas();
} catch (error) {
console.error('❌ Validation failed:', error);
process.exit(1);
@@ -113,8 +139,15 @@ const main = (): void => {
console.log(
` Events: ${COMMON_HTML_EVENTS.length} common events per element`,
);
+
+ console.log(`\nUI Components: ${uiComponents.length} components`);
+ console.log(
+ ` Tags: ${uiComponents.map((component) => component.customElementName).join(', ')}`,
+ );
console.log('');
+ const allComponents = [...htmlElements, ...uiComponents];
+
ensureDirectoriesExist();
const project = createProject();
@@ -122,7 +155,7 @@ const main = (): void => {
console.log('Host files:');
const hostRegistry = generateHostRegistry(
project,
- htmlElements,
+ allComponents,
EVENT_TO_REACT,
);
writeGeneratedFile(
@@ -134,7 +167,7 @@ const main = (): void => {
console.log('\nRemote files:');
const remoteElements = generateRemoteElements(
project,
- htmlElements,
+ allComponents,
HTML_COMMON_PROPERTIES,
COMMON_HTML_EVENTS,
);
@@ -144,7 +177,7 @@ const main = (): void => {
remoteElements.getFullText(),
);
- const remoteComponents = generateRemoteComponents(project, htmlElements);
+ const remoteComponents = generateRemoteComponents(project, allComponents);
writeGeneratedFile(
REMOTE_GENERATED_DIR,
OUTPUT_FILES.REMOTE_COMPONENTS,
diff --git a/packages/twenty-sdk/scripts/remote-dom/generators/host-registry.generator.ts b/packages/twenty-sdk/scripts/remote-dom/generators/host-registry.generator.ts
index f7b5ad672f..64996538b1 100644
--- a/packages/twenty-sdk/scripts/remote-dom/generators/host-registry.generator.ts
+++ b/packages/twenty-sdk/scripts/remote-dom/generators/host-registry.generator.ts
@@ -1,5 +1,6 @@
import type { Project, SourceFile } from 'ts-morph';
+import { isDefined } from 'twenty-shared/utils';
import { CUSTOM_ELEMENT_NAMES } from './constants';
import { type ComponentSchema } from './schemas';
import { addFileHeader, addStatement } from './utils';
@@ -65,12 +66,25 @@ const filterProps = (props: Record) => {
};`;
};
-const generateWrapperComponent = (component: ComponentSchema): string => {
+const generateHtmlWrapperComponent = (component: ComponentSchema): string => {
return `const ${component.name}Wrapper = ({ children, ...props }: { children?: React.ReactNode } & Record) => {
return React.createElement('${component.htmlTag}', filterProps(props), children);
};`;
};
+const generateUiWrapperComponent = (component: ComponentSchema): string => {
+ return `const ${component.name}Wrapper = ({ children, ...props }: { children?: React.ReactNode } & Record) => {
+ return React.createElement(${component.componentImport}, filterProps(props), children);
+};`;
+};
+
+const generateWrapperComponent = (component: ComponentSchema): string => {
+ if (component.isHtmlElement) {
+ return generateHtmlWrapperComponent(component);
+ }
+ return generateUiWrapperComponent(component);
+};
+
const generateRegistryMap = (components: ComponentSchema[]): string => {
const entries = components
.map(
@@ -89,6 +103,28 @@ ${entries}
]);`;
};
+const groupImportsByPath = (
+ components: ComponentSchema[],
+): Map => {
+ const importsByPath = new Map();
+
+ for (const component of components) {
+ if (
+ !component.isHtmlElement &&
+ isDefined(component.componentPath) &&
+ isDefined(component.componentImport)
+ ) {
+ const existing = importsByPath.get(component.componentPath) ?? [];
+ if (!existing.includes(component.componentImport)) {
+ existing.push(component.componentImport);
+ }
+ importsByPath.set(component.componentPath, existing);
+ }
+ }
+
+ return importsByPath;
+};
+
export const generateHostRegistry = (
project: Project,
components: ComponentSchema[],
@@ -110,6 +146,15 @@ export const generateHostRegistry = (
namedImports: ['RemoteFragmentRenderer', 'createRemoteComponentRenderer'],
});
+ const uiImports = groupImportsByPath(components);
+
+ for (const [modulePath, namedImports] of uiImports) {
+ sourceFile.addImportDeclaration({
+ moduleSpecifier: modulePath,
+ namedImports,
+ });
+ }
+
addStatement(sourceFile, generateRuntimeUtilities(eventToReactMapping));
for (const component of components) {
diff --git a/packages/twenty-sdk/scripts/remote-dom/generators/schemas.ts b/packages/twenty-sdk/scripts/remote-dom/generators/schemas.ts
index aaeb4d7c29..c3b230517e 100644
--- a/packages/twenty-sdk/scripts/remote-dom/generators/schemas.ts
+++ b/packages/twenty-sdk/scripts/remote-dom/generators/schemas.ts
@@ -15,6 +15,20 @@ export const HtmlElementConfigZ = z.object({
export const HtmlElementConfigArrayZ = z.array(HtmlElementConfigZ);
+export const UiComponentConfigZ = z.object({
+ tag: z
+ .string()
+ .regex(/^twenty-ui-[a-z0-9-]+$/, 'Tag must start with "twenty-ui-"'),
+ name: z
+ .string()
+ .regex(/^TwentyUi[A-Z]/, 'Name must be PascalCase starting with TwentyUi'),
+ properties: z.record(z.string(), PropertySchemaZ),
+ componentImport: z.string().min(1),
+ componentPath: z.string().min(1),
+});
+
+export const UiComponentConfigArrayZ = z.array(UiComponentConfigZ);
+
export const ComponentSchemaZ = z.object({
name: z.string().min(1),
tagName: z.string().min(1),
@@ -22,9 +36,12 @@ export const ComponentSchemaZ = z.object({
properties: z.record(z.string(), PropertySchemaZ),
events: z.array(z.string()).readonly(),
isHtmlElement: z.boolean(),
- htmlTag: z.string().min(1),
+ htmlTag: z.string().optional(),
+ componentImport: z.string().optional(),
+ componentPath: z.string().optional(),
});
export type PropertySchema = z.infer;
export type HtmlElementConfig = z.infer;
+export type UiComponentConfig = z.infer;
export type ComponentSchema = z.infer;
diff --git a/packages/twenty-sdk/src/front-component-constants/AllowedUiComponents.ts b/packages/twenty-sdk/src/front-component-constants/AllowedUiComponents.ts
new file mode 100644
index 0000000000..b9fb719a75
--- /dev/null
+++ b/packages/twenty-sdk/src/front-component-constants/AllowedUiComponents.ts
@@ -0,0 +1,26 @@
+import { type PropertySchema } from '../front-component/types/PropertySchema';
+
+export type AllowedUiComponent = {
+ tag: string;
+ name: string;
+ properties: Record;
+ componentImport: string;
+ componentPath: string;
+};
+
+export const ALLOWED_UI_COMPONENTS: AllowedUiComponent[] = [
+ {
+ tag: 'twenty-ui-button',
+ name: 'TwentyUiButton',
+ properties: {
+ title: { type: 'string', optional: true },
+ variant: { type: 'string', optional: true },
+ accent: { type: 'string', optional: true },
+ size: { type: 'string', optional: true },
+ disabled: { type: 'boolean', optional: true },
+ fullWidth: { type: 'boolean', optional: true },
+ },
+ componentImport: 'Button',
+ componentPath: 'twenty-ui/input',
+ },
+];
diff --git a/packages/twenty-sdk/src/front-component-constants/index.ts b/packages/twenty-sdk/src/front-component-constants/index.ts
index 9f5919390c..b5a290afe7 100644
--- a/packages/twenty-sdk/src/front-component-constants/index.ts
+++ b/packages/twenty-sdk/src/front-component-constants/index.ts
@@ -9,6 +9,8 @@
export type { AllowedHtmlElement } from './AllowedHtmlElements';
export { ALLOWED_HTML_ELEMENTS } from './AllowedHtmlElements';
+export type { AllowedUiComponent } from './AllowedUiComponents';
+export { ALLOWED_UI_COMPONENTS } from './AllowedUiComponents';
export { COMMON_HTML_EVENTS } from './CommonHtmlEvents';
export { EVENT_TO_REACT } from './EventToReact';
export { HTML_COMMON_PROPERTIES } from './HtmlCommonProperties';
diff --git a/packages/twenty-sdk/src/front-component/host/components/FrontComponentRenderer.tsx b/packages/twenty-sdk/src/front-component/host/components/FrontComponentRenderer.tsx
index 5e68c903bf..a34a877662 100644
--- a/packages/twenty-sdk/src/front-component/host/components/FrontComponentRenderer.tsx
+++ b/packages/twenty-sdk/src/front-component/host/components/FrontComponentRenderer.tsx
@@ -2,19 +2,24 @@ import {
type RemoteReceiver,
RemoteRootRenderer,
} from '@remote-dom/react/host';
-import React, { useState } from 'react';
+import { useState } from 'react';
import { isDefined } from 'twenty-shared/utils';
+
+import { ThemeProvider } from '@emotion/react';
+import { type ThemeType } from 'twenty-ui/theme';
import { FrontComponentWorkerEffect } from '../../remote/components/FrontComponentWorkerEffect';
import { componentRegistry } from '../generated/host-component-registry';
type FrontComponentContentProps = {
componentUrl: string;
onError: (error?: Error) => void;
+ theme: ThemeType;
};
export const FrontComponentRenderer = ({
componentUrl,
onError,
+ theme,
}: FrontComponentContentProps) => {
const [receiver, setReceiver] = useState(null);
@@ -27,10 +32,12 @@ export const FrontComponentRenderer = ({
/>
{isDefined(receiver) && (
-
+
+
+
)}
>
);
diff --git a/packages/twenty-sdk/src/front-component/host/generated/host-component-registry.ts b/packages/twenty-sdk/src/front-component/host/generated/host-component-registry.ts
index f39b002ff0..d3cdb969d5 100644
--- a/packages/twenty-sdk/src/front-component/host/generated/host-component-registry.ts
+++ b/packages/twenty-sdk/src/front-component/host/generated/host-component-registry.ts
@@ -12,6 +12,7 @@ import {
RemoteFragmentRenderer,
createRemoteComponentRenderer,
} from '@remote-dom/react/host';
+import { Button } from 'twenty-ui/input';
const INTERNAL_PROPS = new Set(['element', 'receiver', 'components']);
const EVENT_NAME_MAP: Record = {
@@ -345,6 +346,12 @@ const HtmlHrWrapper = ({
}: { children?: React.ReactNode } & Record) => {
return React.createElement('hr', filterProps(props), children);
};
+const TwentyUiButtonWrapper = ({
+ children,
+ ...props
+}: { children?: React.ReactNode } & Record) => {
+ return React.createElement(Button, filterProps(props), children);
+};
type ComponentRegistryValue =
| ReturnType
| typeof RemoteFragmentRenderer;
@@ -393,5 +400,6 @@ export const componentRegistry: Map = new Map([
['html-td', createRemoteComponentRenderer(HtmlTdWrapper)],
['html-br', createRemoteComponentRenderer(HtmlBrWrapper)],
['html-hr', createRemoteComponentRenderer(HtmlHrWrapper)],
+ ['twenty-ui-button', createRemoteComponentRenderer(TwentyUiButtonWrapper)],
['remote-fragment', RemoteFragmentRenderer],
]);
diff --git a/packages/twenty-sdk/src/front-component/index.ts b/packages/twenty-sdk/src/front-component/index.ts
index 21d35f5940..32c9b1c6ae 100644
--- a/packages/twenty-sdk/src/front-component/index.ts
+++ b/packages/twenty-sdk/src/front-component/index.ts
@@ -54,6 +54,7 @@ export {
HtmlTd,
HtmlBr,
HtmlHr,
+ TwentyUiButton,
} from './remote/generated/remote-components';
export type {
HtmlCommonProperties,
@@ -69,6 +70,7 @@ export type {
HtmlButtonProperties,
HtmlThProperties,
HtmlTdProperties,
+ TwentyUiButtonProperties,
} from './remote/generated/remote-elements';
export {
HtmlDivElement,
@@ -114,6 +116,7 @@ export {
HtmlTdElement,
HtmlBrElement,
HtmlHrElement,
+ TwentyUiButtonElement,
RemoteRootElement,
RemoteFragmentElement,
} from './remote/generated/remote-elements';
diff --git a/packages/twenty-sdk/src/front-component/remote/generated/remote-components.ts b/packages/twenty-sdk/src/front-component/remote/generated/remote-components.ts
index b2dec29bea..5b3014ceac 100644
--- a/packages/twenty-sdk/src/front-component/remote/generated/remote-components.ts
+++ b/packages/twenty-sdk/src/front-component/remote/generated/remote-components.ts
@@ -52,6 +52,7 @@ import {
HtmlTdElement,
HtmlBrElement,
HtmlHrElement,
+ TwentyUiButtonElement,
} from './remote-elements';
export const HtmlDiv = createRemoteComponent('html-div', HtmlDivElement, {
@@ -1126,3 +1127,31 @@ export const HtmlHr = createRemoteComponent('html-hr', HtmlHrElement, {
onDrag: { event: 'drag' },
},
});
+export const TwentyUiButton = createRemoteComponent(
+ 'twenty-ui-button',
+ TwentyUiButtonElement,
+ {
+ eventProps: {
+ onClick: { event: 'click' },
+ onDblclick: { event: 'dblclick' },
+ onMousedown: { event: 'mousedown' },
+ onMouseup: { event: 'mouseup' },
+ onMouseover: { event: 'mouseover' },
+ onMouseout: { event: 'mouseout' },
+ onMouseenter: { event: 'mouseenter' },
+ onMouseleave: { event: 'mouseleave' },
+ onKeydown: { event: 'keydown' },
+ onKeyup: { event: 'keyup' },
+ onKeypress: { event: 'keypress' },
+ onFocus: { event: 'focus' },
+ onBlur: { event: 'blur' },
+ onChange: { event: 'change' },
+ onInput: { event: 'input' },
+ onSubmit: { event: 'submit' },
+ onScroll: { event: 'scroll' },
+ onWheel: { event: 'wheel' },
+ onContextmenu: { event: 'contextmenu' },
+ onDrag: { event: 'drag' },
+ },
+ },
+);
diff --git a/packages/twenty-sdk/src/front-component/remote/generated/remote-elements.ts b/packages/twenty-sdk/src/front-component/remote/generated/remote-elements.ts
index cdcda20e06..254606d588 100644
--- a/packages/twenty-sdk/src/front-component/remote/generated/remote-elements.ts
+++ b/packages/twenty-sdk/src/front-component/remote/generated/remote-elements.ts
@@ -608,6 +608,31 @@ export const HtmlHrElement = createRemoteElement<
properties: HTML_COMMON_PROPERTIES_CONFIG,
events: [...HTML_COMMON_EVENTS_ARRAY],
});
+
+export type TwentyUiButtonProperties = HtmlCommonProperties & {
+ variant?: string;
+ accent?: string;
+ size?: string;
+ disabled?: boolean;
+ fullWidth?: boolean;
+};
+
+export const TwentyUiButtonElement = createRemoteElement<
+ TwentyUiButtonProperties,
+ Record,
+ Record,
+ HtmlCommonEvents
+>({
+ properties: {
+ ...HTML_COMMON_PROPERTIES_CONFIG,
+ variant: { type: String },
+ accent: { type: String },
+ size: { type: String },
+ disabled: { type: Boolean },
+ fullWidth: { type: Boolean },
+ },
+ events: [...HTML_COMMON_EVENTS_ARRAY],
+});
customElements.define('html-div', HtmlDivElement);
customElements.define('html-span', HtmlSpanElement);
customElements.define('html-section', HtmlSectionElement);
@@ -651,6 +676,7 @@ customElements.define('html-th', HtmlThElement);
customElements.define('html-td', HtmlTdElement);
customElements.define('html-br', HtmlBrElement);
customElements.define('html-hr', HtmlHrElement);
+customElements.define('twenty-ui-button', TwentyUiButtonElement);
customElements.define('remote-root', RemoteRootElement);
customElements.define('remote-fragment', RemoteFragmentElement);
@@ -700,6 +726,7 @@ declare global {
'html-td': InstanceType;
'html-br': InstanceType;
'html-hr': InstanceType;
+ 'twenty-ui-button': InstanceType;
'remote-root': InstanceType;
'remote-fragment': InstanceType;
}
diff --git a/packages/twenty-ui/project.json b/packages/twenty-ui/project.json
index 7605e1044d..adb758f9b6 100644
--- a/packages/twenty-ui/project.json
+++ b/packages/twenty-ui/project.json
@@ -4,7 +4,7 @@
"sourceRoot": "packages/twenty-ui/src",
"projectType": "library",
"tags": [
- "scope:frontend"
+ "scope:shared"
],
"targets": {
"build": {
diff --git a/yarn.lock b/yarn.lock
index db4de530ed..9cdb9e8c23 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -57675,6 +57675,7 @@ __metadata:
react: "npm:^18.0.0"
ts-morph: "npm:^25.0.0"
tsx: "npm:^4.7.0"
+ twenty-ui: "workspace:*"
typescript: "npm:^5.9.2"
uuid: "npm:^13.0.0"
vite: "npm:^7.0.0"