## Summary
Aligns **object metadata** icons with the **tinted tile** look
everywhere we show a workspace object, and **retires** the
navigation-only `NavigationMenuItemStyleIcon` wrapper in favor of
**shared** UI primitives under `@/ui/display` and `@/object-metadata`.
## What changed
### Global tinted icon building blocks (`@/ui/display`)
- **`TintedIconTile`** / **`StyledTintedIconTileContainer`** support
optional **`size`** and **`stroke`**, and grow the tile when **`size`**
is set so layouts match previous `theme.icon` usage.
- Shared helpers and constants for theme color parsing and tinted
backgrounds/borders/icon color (e.g.
**`getTintedIconTileStyleFromColor`**, **`parseThemeColor`**,
**`getColorFromTheme`**, related constants).
### Object metadata icon (`@/object-metadata`)
- **`ObjectMetadataIcon`** composes **`TintedIconTile`** with
**`getObjectColorWithFallback`**, forwards optional **`size`** /
**`stroke`** for **visual parity** with old `getIcon` + explicit sizing.
- **`getSelectOptionIconFromObjectMetadataItem`** returns an
**`IconComponent`** for selects/menus that expect a component, not a
React node.
### Navigation module cleanup
- **Removed** **`NavigationMenuItemStyleIcon`**; call sites use
**`ObjectMetadataIcon`**, **`TintedIconTile`**, and/or the shared
**`getTintedIconTileStyleFromColor`** pipeline so the same treatment is
**not** tied to the navigation package.
- **`NavigationMenuItemIcon`**, view/link overlays, DnD handle, and
sidebar editor flows updated to use the shared pattern where they render
object (or tinted) icons.
### Product surfaces updated (non-exhaustive)
- **Settings:** role object picker/rows, data model
tables/graph/overview, object preview summary, webhooks entity list,
morph relation multiselect.
- **Workflows:** create/update/delete/upsert/find records, triggers,
filters, variables dropdowns, AI agent object rows, object dropdowns.
- **Shell:** side panel object filter / data sources / folder chrome
where object icons appear.
- **Records:** index header icon, show breadcrumb styling.
- **Activity:** timeline event icon when linked object metadata applies.
- **`NavigationDrawerItem`:** tinted branch aligned with shared
**`TintedIconTile`** behavior.
---------
Co-authored-by: Etienne <45695613+etiennejouan@users.noreply.github.com>
## Summary
Now that Twenty has fully migrated from Emotion to Linaria, the theme
system has been simplified to remove unnecessary complexity that existed
only to support the old runtime injection pattern.
### What changed
- **Deleted** `generateThemeConstants.ts` script and the entire
`generated/` directory — no more auto-generation
- **Added** `theme-light.css` and `theme-dark.css`: static CSS files
with 991 custom properties each, scoped under `.light` and `.dark`
selectors respectively
- **Moved** `themeCssVariables.ts` out of `generated/` and hand-maintain
it as a static `as const` object of `var(--t-*)` references (Linaria can
statically evaluate these at build time)
- **Extracted** numeric constants (`MOBILE_VIEWPORT`, `ICON_SIZES`,
`ICON_STROKES`) into a new `constants.ts` — CSS variables can't be used
in media queries or as numeric icon size props
- **Simplified** `ThemeContextProvider`: removed
`ThemeCssVariableInjectorEffect` entirely; now uses a single
`useLayoutEffect` to toggle `.light`/`.dark` class on `<html>`
- **Added** `class="light"` to `index.html` as default to prevent FOUC
before React hydration
### Why
The previous setup maintained a dual system: JS theme objects
(`THEME_LIGHT`/`THEME_DARK`) used at runtime, plus a generation script
that produced CSS variable entry arrays, which were then injected into
the DOM by `ThemeCssVariableInjectorEffect`. With Linaria, theme values
only need to be CSS custom properties — the JS objects were redundant.
This PR removes ~250 lines of infrastructure while keeping the same
theming capabilities.
## Migrate twenty-ui from Emotion to Linaria
Completes the migration of all `twenty-ui` components from Emotion
(runtime CSS-in-JS) to Linaria (zero-runtime, CSS extracted at build
time).
- Replaced `@emotion/styled` with `@linaria/react` across ~170 files
- Removed all Emotion dependencies from `twenty-ui`
- Introduced a CSS custom properties-based theme system:
`themeCssVariables` where every leaf is a `var(--t-xxx)` reference,
injected onto `document.documentElement` by
`ThemeCssVariableInjectorEffect`
- No more `theme` prop threading — styled components reference
`themeCssVariables.x.y` directly at build time
- Updated `twenty-front` consumers to remove `theme={theme}` prop
passing
**Before / After:**
```tsx
// Emotion
color: ${({ theme }) => theme.font.color.primary};
padding: ${({ theme }) => theme.spacing(4)};
// Linaria
color: ${themeCssVariables.font.color.primary};
padding: ${themeCssVariables.spacing[4]};
```
### Theme architecture
Two build-time utilities produce the theme system:
- **`buildThemeReferencingRootCssVariables`** — walks the theme object
and builds a nested mirror where every leaf is a `var(--t-xxx)` string
(evaluated at build time by wyw-in-js)
- **`prepareThemeForRootCssVariableInjection`** — walks the runtime
theme and collects flat `[--css-variable-name, value]` pairs, injected
onto `document.documentElement` by `ThemeCssVariableInjectorEffect`
Both share naming conventions (`camelToKebab`, `SPACING_VALUES`,
`formatSpacingKey`) and are unit tested.
### Spacing cleanup
Spacing scale now uses integers 0–32 (generated via loop), with `0.5`
and `1.5` as the only fractional exceptions. All other fractional
spacing usages (`0.25`, `0.75`, `1.25`, `2.5`, `3.5`) were replaced with
literal pixel values across ~20 twenty-front files.
### Framer Motion integration
Linaria doesn't support `styled(motion.div)` — wrapping a motion element
with `styled()` causes the component body to be stripped at build time.
Instead, we define the styled component first, then wrap it with
`motion.create()`:
```tsx
const StyledBarBase = styled.div`
background-color: ${themeCssVariables.font.color.primary};
height: 100%;
`;
const StyledBar = motion.create(StyledBarBase);
```
### Block interpolations
Linaria doesn't support interpolations that return multiple CSS
declarations (Linaria wraps the entire block in a single `var()`,
producing invalid CSS). These were split into individual property
interpolations:
```tsx
// Emotion — single interpolation returning multiple declarations
border-left: ${({ divider, theme }) => {
const border = `1px solid ${theme.border.color.light}`;
return divider ? `border-${divider}: ${border}` : '';
}}
// Linaria — one interpolation per property
border-left: ${({ divider }) =>
divider === 'left' ? `1px solid ${themeCssVariables.border.color.light}` : 'none'};
border-right: ${({ divider }) =>
divider === 'right' ? `1px solid ${themeCssVariables.border.color.light}` : 'none'};
```
### Dynamic styles via CSS variables
When a component needs to compute styles from multiple props with
complex branching logic (e.g. `Button` combining `variant`, `accent`,
`inverted`, `disabled`, `focus`, `position`), Linaria's prop
interpolations become unwieldy. In those cases we use a
`computeDynamicStyles` function that returns a `CSSProperties` object
injected via `style={}`, referenced from the static CSS with `var()`:
```tsx
const StyledButton = styled.button`
background: var(--btn-bg);
border-color: var(--btn-border-color);
&:hover { background: var(--btn-hover-bg); }
`;
const dynamicStyles = useMemo(() => {
const s = computeButtonDynamicStyles(variant, accent, ...);
return { '--btn-bg': s.background, '--btn-hover-bg': s.hoverBackground } as CSSProperties;
}, [variant, accent, ...]);
return <StyledButton style={dynamicStyles} />;
```
### CSS var + unit concatenation
CSS custom properties can't be concatenated with unit suffixes directly
(`var(--x)px` is invalid). Values that need units use `calc()`:
```tsx
// Broken
transition: background ${themeCssVariables.animation.duration.instant}s ease;
// Fixed
transition: background calc(${themeCssVariables.animation.duration.instant} * 1s) ease;
```
## Remove Recoil from twenty-ui
Completely removes the `recoil` dependency from `twenty-ui` by
converting all atoms, hooks, and providers to Jotai equivalents.
### twenty-ui
- `createState` now returns a Jotai `PrimitiveAtom` instead of a Recoil
atom
- `iconsState`, `IconsProvider`, `useIcons` converted to Jotai
(`useSetAtom`, `useAtomValue`)
- `RecoilRootDecorator` now uses Jotai `Provider` (name kept for compat)
- Deleted unused `invalidAvatarUrlsState` (Avatar already uses
`invalidAvatarUrlsAtomV2`)
- Removed `recoil` from `package.json`
### twenty-front
- Created local Recoil `createState` at
`@/ui/utilities/state/utils/createState` for ~112 state files still on
Recoil
- Updated all imports accordingly
- Removed `iconsState` from Recoil snapshot preservation in `useAuth`
(lives in Jotai store now)
## Summary
Moves the custom ESLint rules from `tools/eslint-rules` to
`packages/twenty-eslint-rules` for better organization within the
monorepo packages structure.
## Changes
- Move `eslint-rules` from `tools/` to `packages/twenty-eslint-rules`
- Use `loadWorkspaceRules` from `@nx/eslint-plugin` to load custom rules
- Update all ESLint configs to use the `twenty/` rule prefix instead of
`@nx/workspace-`
- Update `project.json`, `jest.config.mjs` with new paths
- Update `package.json` workspaces and `nx.json` cache inputs
- Update Dockerfile reference
## Technical Details
The custom ESLint rules are now loaded using Nx's `loadWorkspaceRules`
utility which:
- Handles TypeScript transpilation automatically
- Allows loading workspace rules from any directory
- Provides a cleaner approach than the previous `@nx/workspace-`
convention
## Testing
- Verified all 17 custom ESLint rules load correctly from the new
location
- Verified linting works on dependent packages (twenty-front,
twenty-server, etc.)
There are three identified scenarios.
## First scenario
The user uses a desktop. They click on a company, which is opened in the
side panel. They click on a tab, like "Tasks". The user chooses to open
the record in fullscreen. The "Tasks" tab is opened by default in the
fullscreen record page.
If the user refreshes the page, the default tab is shown: "Timeline".
**This was the behavior on the show pages, and I kept it.** If we wanted
to show the "Tasks" tab here, we would have to put the id of the "Tasks"
tab in the URL hash when we open the record in fullscreen and an active
tab id is already set.
https://github.com/user-attachments/assets/205776ac-9ad7-4e79-af9a-6b44360a5d77
## Second scenario
The user uses a desktop or a mobile device and interacts with a company
record. They click on a tab. If they refresh, the selected tab will be
displayed by default.
### Desktop
https://github.com/user-attachments/assets/d4e1908b-a591-42f6-bb82-5aec592e2778
### Mobile
https://github.com/user-attachments/assets/7bab73cc-a2ff-4ce8-9bc6-325efef2d500
## Third scenario
The user uses a desktop. They click on a dashboard, which is opened in
fullscreen mode by default. They select tabs. If they refresh, the last
selected tab is opened by default.
When the user turns edit mode on, **the url hash is removed**. The last
selected tab remains selected. If the user selects another tab, the url
hash isn't set, but the newly selected tab is displayed correctly. If
the user saves their changes, the selected tab remains selected.
However, if they refresh the page, the default tab replaces the
previously selected tab.
Not setting the url hash when editing a page layout simplifies the code.
I'm okay with this tradeoff as the feature is primarily meant to let
users share specific tabs of their records.
**This behavior will also be used for record page layout once edit mode
is supported.**
https://github.com/user-attachments/assets/869657a1-6895-4ade-816c-972c77aaab9e
Closes https://github.com/twentyhq/core-team-issues/issues/1784
# Complete color refactoring
Closes https://github.com/twentyhq/core-team-issues/issues/1779
- Updated all colors to use Radix colors with P3 color space allowing
for brighter colors
- Created our own gray scale interpolated on Radix's scale to have the
same values for grays as the old ones in the app
- Introduced dark and light colors as well as there transparent versions
- Added many new colors from radix that can be used in the tags or in
the graphs
- Updated multiple color utilities to match new behaviors
- Changed the computation of Avatar colors to return only colors from
the theme (before it was random hsl)
These changes allow the user to use new colors in tags or charts, the
colors are brighter and with better contrast. We have a full range of
color variations from 1 to 12 where before we only had 4 adaptative
colors.
All these changes will allow us to develop custom themes for the user
soon, where users can choose their accent colors, background colors and
there contrast.
Context :
Plan choice [on pricing page on website](https://twenty.com/pricing)
should redirect you the right plan on app /plan-required page (after
sign in), thanks to query parameters and BillingCheckoutSessionState
sync.
With email verification, an other session starts at CTA click in
verification email. Initial BillingCheckoutSessionState is lost and user
can't submit to the plan he choose.
Solution :
Pass a nextPath query parameter in email verification link
To test :
- Modify .env to add IS_BILLING_ENABLED (+ reset db + sync billing) +
IS_EMAIL_VERIFICATION_REQUIRED
- Start test from this page
http://app.localhost:3001/welcome?billingCheckoutSession={%22plan%22:%22ENTERPRISE%22,%22interval%22:%22Year%22,%22requirePaymentMethod%22:true}
- After verification, check you arrive on /plan-required page with
Enterprise plan on a yearly interval (default is Pro/monthly).
closes https://github.com/twentyhq/twenty/issues/12288
Small optimization for faster loading (gaining ~80ms - average time of a
click)
It might seem a little over-engineered but there are a lot of edge cases
and I couldn't find a simpler solution
I also tried to tackle Link Chips but it's more complex so this will be
for another PR
Changes for performance improvement.
The primary improvements include replacing GraphQL queries with
REST-based client configuration fetching and making the client config
non render-blocking
# Introduction
closes https://github.com/twentyhq/core-team-issues/issues/591
Same than for `twenty-shared` made in
https://github.com/twentyhq/twenty/pull/11083.
## TODO
- [x] Manual migrate twenty-website twenty-ui imports
## What's next:
- Generate barrel and migration script factorization within own package
+ tests
- Refactoring using preconstruct ? TimeBox
- Lint circular dependencies
- Lint import from barrel and forbid them
### Preconstruct
We need custom rollup plugins addition, but preconstruct does not expose
its rollup configuration. It might be possible to handle this using the
babel overrides. But was a big tunnel.
We could give it a try afterwards ! ( allowing cjs interop and stuff
like that )
Stuck to vite lib app
Closed related PRs:
- https://github.com/twentyhq/twenty/pull/11294
- https://github.com/twentyhq/twenty/pull/11203
# Introduction
In this PR we've migrated `twenty-shared` from a `vite` app
[libary-mode](https://vite.dev/guide/build#library-mode) to a
[preconstruct](https://preconstruct.tools/) "atomic" application ( in
the future would like to introduce preconstruct to handle of all our
atomic dependencies such as `twenty-emails` `twenty-ui` etc it will be
integrated at the monorepo's root directly, would be to invasive in the
first, starting incremental via `twenty-shared`)
For more information regarding the motivations please refer to nor:
- https://github.com/twentyhq/core-team-issues/issues/587
-
https://github.com/twentyhq/core-team-issues/issues/281#issuecomment-2630949682
close https://github.com/twentyhq/core-team-issues/issues/589
close https://github.com/twentyhq/core-team-issues/issues/590
## How to test
In order to ease the review this PR will ship all the codegen at the
very end, the actual meaning full diff is `+2,411 −114`
In order to migrate existing dependent packages to `twenty-shared` multi
barrel new arch you need to run in local:
```sh
yarn tsx packages/twenty-shared/scripts/migrateFromSingleToMultiBarrelImport.ts && \
npx nx run-many -t lint --fix -p twenty-front twenty-ui twenty-server twenty-emails twenty-shared twenty-zapier
```
Note that `migrateFromSingleToMultiBarrelImport` is idempotent, it's atm
included in the PR but should not be merged. ( such as codegen will be
added before merging this script will be removed )
## Misc
- related opened issue preconstruct
https://github.com/preconstruct/preconstruct/issues/617
## Closed related PR
- https://github.com/twentyhq/twenty/pull/11028
- https://github.com/twentyhq/twenty/pull/10993
- https://github.com/twentyhq/twenty/pull/10960
## Upcoming enhancement: ( in others dedicated PRs )
- 1/ refactor generate barrel to export atomic module instead of `*`
- 2/ generate barrel own package with several files and tests
- 3/ Migration twenty-ui the same way
- 4/ Use `preconstruct` at monorepo global level
## Conclusion
As always any suggestions are welcomed !
# Introduction
closes#10196
Initially fixing the `Account Owner` record field value should not be
clickable and redirects on current page bug.
This has been fixed computing whereas the current filed is a workspace
member dynamically rendering a stale Chip components instead of an
interactive one
## Refactor
Refactored the `AvatarChip` `to` props logic to be scoped to lower level
scope `Chip`.
Now we have `LinkChip` `Chip`, `LinkAvatarChip` and `AvatarChip` all
exported from twenty-ui.
The caller has to determine which one to call from the design system
## New rule regarding chip links
As discussed with @charlesBochet and @FelixMalfait
A chip link will now ***always*** have `to` defined. ( and optionally an
`onClick` ).
`ChipLinks` cannot be used as buttons anymore
## Factorization
Deleted the `RecordIndexRecordChip.tsx` file ( aka
`RecordIdentifierChip` component ) that was duplicating some logic,
refactored the `RecordChip` in order to handle what was covered by
`RecordIdentifierChip`
## Conclusion
As always any suggestions are more than welcomed ! Took few opinionated
decision/refactor regarding nested long ternaries rendering `ReactNode`
elements
## Misc
https://github.com/user-attachments/assets/8ef11fb2-7ba6-4e96-bd59-b0be5a425156
---------
Co-authored-by: Mohammed Razak <mohammedrazak2001@gmail.com>
Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
# Introduction
Avoid having multiple `isDefined` definition across our pacakges
Also avoid importing `isDefined` from `twenty-ui` which exposes a huge
barrel for a such little util function
## In a nutshell
Removed own `isDefined.ts` definition from `twenty-ui` `twenty-front`
and `twenty-server` to move it to `twenty-shared`.
Updated imports for each packages, and added explicit dependencies to
`twenty-shared` if not already in place
Related PR https://github.com/twentyhq/twenty/pull/9941
- remove asynchronous serverless function build
- build serverless function synchronously instead on activate workflow
or execute
- add a loader on workflow code step test tab test button
- add a new `ServerlessFunctionSyncStatus` `BUILDING`
- add a new route to build a serverless function draft version
- delay artificially execution to avoid UI flashing
https://github.com/user-attachments/assets/8d958d9a-ef41-4261-999e-6ea374191e33
Fixes#8601
We had 3 implementations of getImageAbsoluteURI: in twenty-front, in
twenty-ui and in twenty-emails. I was able to remove the one in
twenty-front but I could not remove it from twenty-emails as this is a
standalone for now. The vision is to introduce shared utils in a
twenty-shared package
The `QueryResultGettersFactory` that is called on every query return to
was called only on the first level of relations because recursivity
wasn't implemented.
In this PR I implement recursivity and add some typing for the possible
forms a GraphQL query field can take.
This PR will fix any issue we have with pictures that were losing their
token (here for person.avatarUrl)
Fixes https://github.com/twentyhq/twenty/issues/8425
Fixes https://github.com/twentyhq/twenty/issues/8498
---------
Co-authored-by: Weiko <corentin@twenty.com>
This PR was created by [GitStart](https://gitstart.com/) to address the
requirements from this ticket:
[TWNTY-7536](https://clients.gitstart.com/twenty/5449/tickets/TWNTY-7536).
---
### Description
Migrate all menu items components to twenty ui and update imports.
```typescript
MenuItem
MenuItemAvata
MenuItemCommand
MenuItemCommandHotKeys
MenuItemDraggable
MenuItemMultiSelect
MenuItemMultiSelectAvatar
MenuItemMultiSelectTag
MenuItemNavigate
MenuItemSelect
MenuItemSelectAvatar
MenuItemSelectColor
MenuItemSelectTag
MenuItemSuggestion
MenuItemToggle
```
\
Also migrate all other dependent components and utilities like
`Checkbox` & `Toggle`\
\
Fixestwentyhq/private-issues#82
---------
Co-authored-by: gitstart-twenty <gitstart-twenty@users.noreply.github.com>
Co-authored-by: gitstart-twenty <140154534+gitstart-twenty@users.noreply.github.com>
Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
Fixes: #6436
Changes made:
- Added typecheck step before twenty-ui build to check stories TS errors
- Added a tsconfig.dev.json to add stories and tests to typecheking when
in dev mode
- Added tsconfig.dev.json to storybook dev command of twenty-ui to
typecheck stories while developing
- Fixed twenty-ui stories that were broken
- Added a serve command to serve front build
- Fixed unit test from another PR
---------
Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>