This PR adds the necessary tool to create a demo workspace with :
relevant custom objects and fields, mock data and a real dashboard with
graph widgets.
It is still a bit under-optimized and slow but it works.
This PR also adds an AI tool that allows to see what happens in real
time, it navigates the app and waits when necessary.
---------
Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
## 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;
```
Adds color support for navigation menu items.
---------
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
Co-authored-by: Devessier <baptiste@devessier.fr>
Co-authored-by: Charles Bochet <charles@twenty.com>
This PR pgrades all BlockNote packages (@blocknote/core,
@blocknote/react, @blocknote/mantine, @blocknote/server-util,
@blocknote/xl-docx-exporter, @blocknote/xl-pdf-exporter) to 0.47.0 and
adapts the codebase to the new API.
### Changes
- Dependency upgrades: Bumped all BlockNote packages to 0.47.0, added
required Mantine v8 peer dependencies, removed unnecessary prosemirror
resolutions
- Formatting toolbar: Replaced the manual reimplementation of
FormattingToolbarController (which handled visibility, positioning,
portal rendering, text-alignment-based placement, and a
dangerouslySetInnerHTML transition trick) with BlockNote's built-in
FormattingToolbarController. The toolbar buttons themselves are
unchanged.
- Side menu: Replaced manual drag handle menu positioning and rendering
(DashboardBlockDragHandleMenu, DashboardBlockColorPicker, and their
floating configs) with BlockNote's built-in SideMenuController,
DragHandleButton, and DragHandleMenu components. Deleted 4 files that
became dead code.
- Extension API migration: Replaced deprecated editor.suggestionMenus
and editor.formattingToolbar APIs with the new extension system
(SuggestionMenu, useExtensionState, editor.getExtension())
- Slash menu fixes: Filtered out BlockNote's new default "File" item
(added in 0.47) to avoid duplicates with our custom one; added icon
mappings for new block types (Toggle List, Divider, Toggle Headings,
Headings 4-6)
- Server-side: Switched @blocknote/server-util to dynamic import() to
handle ESM-only transitive dependencies in CJS context
## Summary
Removes the `recoil` dependency entirely from `package.json` and
`twenty-front/package.json`, completing the migration to Jotai as the
sole state management library.
Removes all Recoil infrastructure: `RecoilRoot` wrapper from `App.tsx`
and test decorators, `RecoilDebugObserver`, Recoil-specific ESLint rules
(`use-getLoadable-and-getValue-to-get-atoms`,
`useRecoilCallback-has-dependency-array`), and legacy Recoil utility
hooks/types (`useRecoilComponentState`, `useRecoilComponentValue`,
`createComponentState`, `createFamilyState`, `getSnapshotValue`,
`cookieStorageEffect`, `localStorageEffect`, etc.).
Renames all `V2`-suffixed Jotai state files and types to their canonical
names (e.g., `ComponentStateV2` -> `ComponentState`,
`agentChatInputStateV2` -> `agentChatInputState`, `SelectorCallbacksV2`
-> `SelectorCallbacks`), and removes the now-redundant V1 counterparts.
Updates ~433 files across the codebase to use the renamed Jotai imports,
remove Recoil imports, and clean up test wrappers (`RecoilRootDecorator`
-> `JotaiRootDecorator`).
## Summary
- update AI chat message typography and list line-height for readability
- apply richer markdown-section styling for headings, spacing,
separators, and inline code
- keep links non-underlined by default with underline on hover, using
accent11 for link color
- preserve previous AI chat table design while keeping other markdown
improvements
## Validation
- yarn eslint
packages/twenty-front/src/modules/ai/components/LazyMarkdownRenderer.tsx
packages/twenty-front/src/modules/ai/components/AIChatMessage.tsx
---------
Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
## PR description
This PR:
- Creates a TypeScript-based extractor that discovers all exported
twenty-ui components by scanning barrel files, extracting
props/slots/events via ts-morph type analysis, and generating the remote
DOM bindings automatically.
- Adds a new ESLint rule which enforces all *Props types in twenty-ui
components to be exported, which is required for the extractor to
discover component prop types. Existing twenty-ui components are updated
to comply with this rule.
- Extends the remote DOM generation to support slots, per-component
events, forwardRef wrappers, and richer property types (array, object,
function)
## Edge cases to fix in another PR
- Icons cannot be rendered inside buttons
- IconButtons throw an error when mounted
- MenuItems are not displayed correctly
- MenuItemNavigate throws on click
## Video Demo
https://github.com/user-attachments/assets/c2ed67cf-6a15-4896-9fec-e83fac0e862b
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
Co-authored-by: Devessier <baptiste@devessier.fr>
https://github.com/user-attachments/assets/c4e63edb-6e98-44bc-841f-ee110ae712d4
How it works
- `manifest.json` files are now committed when apps are published to our
repo
- to display available apps, from the server, we read into our github
repo, using a pod-scoped cache
- feature flagged
- app installation will be behind permission gate MARKETPLACE_APPS
Limitations and what is yet to develop
- content and settings tabs
- installed apps tab
- app installation
- test and potentially fix reading from .manifest.json once [Reshape
manifest
structure](https://github.com/twentyhq/core-team-issues/issues/2183) is
done. additional work is expected on assets notably. (couldnt properly
do it here as manifest.json will only be committed after this pr)
- we only read in community/ folder for now - we may want tochange that
- the cache is rather artisanal for now and scoped by pod - we may want
to change that
In this PR :
- New field type FieldMetadataType.FILES added (as jsonb in DB)
- Backend: GraphQL types, validation, REST API schema, data processor
handling
- Frontend: field configuration in settings
- Feature flag IS_FILES_FIELD_ENABLED to gate the feature
- Integration tests for create/filter validation
- Dev seed: Feature flag enabled + FILES field on survey result object
To do in next PRs :
- Backend :
-- new workspaceFile controller (for download) & new workspaceFile
upload resolver (for upload)
-- pre-hook/post-hook to ensure fileId existence + listener to ensure
cleaning
- Frontend : FILES field display/edit in table view, record, ...
## Summary
- Update all illustration icons to use `theme.accent.accent3` for fill
and `theme.accent.accent8` for border
- Replaces hardcoded `IllustrationIcon.blue` values with
theme-responsive accent colors
- Icons will now adapt correctly to theme changes
## Test plan
- [ ] Navigate to Settings > Data model > select any object
- [ ] Verify the Relations "Type" column icons use the accent colors
- [ ] Verify the Fields "Data type" column icons use the accent colors
- [ ] Switch themes (if available) and verify icons adapt accordingly
## Summary
This PR introduces row-level security (RLS) permissions for roles in the
frontend, allowing fine-grained access control at the record level.
Users can now define permission rules that determine which specific
records a role can access based on dynamic conditions and filters.
## What's Changed
Implemented UI for configuring record-level permissions on object
permissions screens
Added support for defining permission predicates using filter conditions
(similar to advanced filters)
Introduced variable picker for dynamic permission rules (e.g., "me"
context for user-specific access)
Built predicate conversion layer to sync UI state with backend
permission structure
Extended GraphQL schema with mutations for upserting row-level
permission predicates
Fixed handling of orphaned RLS groups to prevent data inconsistencies
Added enterprise key validation for RLS features
The implementation enables scenarios like "users can only see their own
records" or "users can access records associated with their team."
<img width="687" height="702" alt="Screenshot 2026-01-09 at 23 07 02"
src="https://github.com/user-attachments/assets/33fe736e-6cbf-40bd-b2eb-c8a90c8d21bc"
/>
---------
Co-authored-by: Charles Bochet <charles@twenty.com>
## Description
SELECT fields have a defined option order that users expect to see
reflected in charts.
This PR allows sorting by that position and also enables custom manual
ordering.
## Video QA
### Reordering on primary axis
https://github.com/user-attachments/assets/994f515e-19cb-4a5e-b745-e8c77e92ae0b
### Reordering on secondary axis
https://github.com/user-attachments/assets/444c16f2-1920-4dc4-8b42-312d520ab43b
Note: The colors in the graph will match the colors of the select
options, but this will be done in another PR
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> Introduces new sort modes and UI for chart groupings, with full FE/BE
support and updated GraphQL schema.
>
> - Extend `GraphOrderBy` with `FIELD_POSITION_ASC/DESC` and `MANUAL`;
add corresponding fields in configs: `primaryAxisManualSortOrder`,
`secondaryAxisManualSortOrder`, and `manualSortOrder` (pie)
> - New UI: dropdown options filtered by field type, icons, and a
draggable submenu (`ChartManualSortSubMenuContent`) to reorder select
options; integrates with widget edit flow
> - Sorting logic added/refactored: `sortChartData`,
`sortByManualOrder`, `sortBySelectOptionPosition`,
`sortLineChartSeries`, plus updates to bar/line/pie transformers to
honor new modes and manual orders
> - Default behaviors: select fields default to `FIELD_POSITION_ASC`;
query variable builders skip `orderBy` when using manual/position sorts
> - Update GraphQL generated types/fragments/queries and backend
DTOs/schemas to persist new fields; add tests for sorting utilities and
snapshots; add sorting icons in `twenty-ui`
>
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
78c9b56c0f1f2d45f7f8b270bb59ca599a005abe. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
---------
Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
#16383
## Scope & Context
**Context**:
Added a **Bulk Update** feature to allow users to modify multiple
records simultaneously. This addresses the need for efficient data
management when dealing with large datasets.
**Scope**:
Enabling a "Bulk Update" flow that can be triggered for a filtered list
of records (e.g., current view). The feature guides the user through
selecting fields to modify, inputting new values, and executing the
update with real-time progress feedback.
## Current behavior
Currently, users must open and update records one by one.
To change the "Status" of 10 different opportunities, the user has to
navigate to each record detail page or use the inline cell editor 10
separate times. This is repetitive and time-consuming.
## Expected behavior
Users can trigger "Update Records" from the command menu or action bar.
1. **Choose Fields**: A step where users select which properties they
want to modify (e.g., check "Status" and "Assignee").
2. **Input Values**: Users provide the new values for the selected
fields.
3. **Execution**: Upon confirmation, the system updates all matching
records in the background.
4. **Feedback**: A progress indicator shows the number of processed
records, and a toast notification confirms completion.
*Key interactions:*
- Users can clear a field's value (set to null) by selecting the field
but leaving the input empty.
- The operation supports cancelling midway.
https://github.com/user-attachments/assets/c87366a3-246e-4615-9941-0bf63d70df86
## Technical inputs
**Core Functionality**:
- **Incremental Batch Processing**: Updates are performed in small
batches (using `useIncrementalUpdateManyRecords` to handle large
datasets without timing out or freezing the UI.
- **Global Feedback**: Integrated with the Snackbar system to notify
users of success or errors across the application.
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> Introduces localization keys and generated messages to support the new
bulk update workflow.
>
> - New strings for command menu selection info: `{totalCount} selected`
> - Footer actions for bulk update: `Apply`, `Cancel` in
`UpdateMultipleRecordsFooter`
> - Toasts in `UpdateMultipleRecordsContainer`: `Successfully updated
{count} records`, `Failed to update records. Please try again.`
> - Action labels: `Update`, `Update records` in
`DefaultRecordActionsConfig` and command menu hook
> - Adds/updates entries across multiple locale `.po` files and
regenerates `af-ZA` messages
>
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
b1dce4c599b85fa2947ca63b265fc750e80d9bb6. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
---------
Co-authored-by: Samuel Arbibe <samuelarbibe@Samuels-MacBook-Pro.local>
Co-authored-by: Félix Malfait <felix@twenty.com>
- Rename page title from 'Releases' to 'Updates'
- Rename navigation item label from 'Releases' to 'Updates'
- Remove tabbed interface (Changelog/Lab tabs)
- Add 'Releases' section with external link to changelog
- Add 'Early access' section with lab features
- Add IconTransform to twenty-ui exports
- Delete unused tab-related components and constants
Screenshot:
<img width="2158" height="1698" alt="CleanShot 2025-12-17 at 17 53
46@2x"
src="https://github.com/user-attachments/assets/87ead041-24a7-4afb-9dfc-71e5c20324d6"
/>
---------
Co-authored-by: Charles Bochet <charles@twenty.com>
## Summary
- Add code interpreter tool that enables AI to execute Python code for
data analysis, CSV processing, and chart generation
- Support for both local (development) and E2B (sandboxed production)
execution drivers
- Real-time streaming of stdout/stderr and generated files
- Frontend components for displaying code execution results with
expandable sections
## Code Quality Improvements
- Extract `getMimeType` to shared utility to reduce code duplication
between drivers
- Fix security issue: escape single quotes/backslashes in E2B driver env
variable injection
- Add `buildExecutionState` helper to reduce duplicated state object
construction
- Add `DEFAULT_CODE_INTERPRETER_TIMEOUT_MS` constant for consistency
- Fix lingui linting warning and TypeScript theme errors in frontend
## Test Plan
- [ ] Test code interpreter with local driver in development
- [ ] Test code interpreter with E2B driver in production environment
- [ ] Verify streaming output displays correctly in chat UI
- [ ] Verify generated files (charts, CSVs) are uploaded and
downloadable
- [ ] Test file upload flow (CSV, Excel) triggers code interpreter
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> Updates generated i18n catalogs for Polish and pseudo-English, adding
strings for code execution/output (code interpreter) and various UI
messages, with minor text adjustments.
>
> - **Localization**:
> - **Generated catalogs**: Refresh `locales/generated/pl-PL.ts` and
`locales/generated/pseudo-en.ts`.
> - Add strings for code execution/output (e.g., code, copy code/output,
running/waiting states, download files, generated files, Python code
execution).
> - Include new UI texts (errors, prompts, menus) and minor text
corrections.
> - No changes to `pt-BR`; other files unchanged functionally.
>
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
befc13d02c21e5a6647bc1aa6daa2a89f60b7ef8. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
## Overview
This PR strengthens our permission system by introducing more granular
role-based access control across the platform.
## Changes
### New Permissions Added
- **Applications** - Control who can install and manage applications
- **Layouts** - Control who can customize page layouts and UI structure
- **AI** - Control access to AI features and agents
- **Upload File** - Separate permission for file uploads
- **Download File** - Separate permission for file downloads (frontend
visibility)
### Security Enhancements
- Implemented whitelist-based validation for workspace field updates
- Added explicit permission guards to core entity resolvers
- Enhanced ESLint rule to enforce permission checks on all mutations
- Created `CustomPermissionGuard` and `NoPermissionGuard` for better
code documentation
### Affected Components
- Core entity resolvers: webhooks, files, domains, applications,
layouts, postgres credentials
- Workspace update mutations now use whitelist validation
- Settings UI updated with new permission controls
### Developer Experience
- ESLint now catches missing permission guards during development
- Explicit guard markers make permission requirements clear in code
review
- Comprehensive test coverage for new permission logic
## Testing
- ✅ All TypeScript type checks pass
- ✅ ESLint validation passes
- ✅ New permission guards properly enforced
- ✅ Frontend UI displays new permissions correctly
## Migration Notes
Existing workspaces will need to assign the new permissions to roles as
needed. By default, all new permissions are set to `false` for non-admin
roles.
Adds intelligent routing system that automatically selects the best
agent for user queries based on conversation context.
### Changes:
- Added `routerModel` column to workspace table for configurable router
LLM selection
- Implemented `RouterService` with conversation history analysis and
agent matching logic
- Created router settings UI in AI Settings page with model dropdown
- Removed agent-specific thread associations - threads are now
agent-agnostic
- Added real-time routing status notification in chat UI with shimmer
effect
- Removed automatic default assistant agent creation
- Renamed GraphQL operations from agent-specific to generic (e.g.,
`agentChatThreads` → `chatThreads`)
---------
Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
Co-authored-by: Félix Malfait <felix@twenty.com>
### Summary
Split BAR into VERTICAL_BAR and HORIZONTAL_BAR as separate chart types.
### The Problem
Initially wanted a simple vertical/horizontal toggle for bar charts, but
ran into a GraphQL union type
constraint: union types can't have the same field name with different
nullability.
- Vertical bars need groupByFieldMetadataIdX as required (categories on
X)
- Horizontal bars need groupByFieldMetadataIdY as required (categories
on Y)
- GraphQL schema generation fails with this setup
### The Solution
Use semantic primaryAxis and secondaryAxis naming that's
orientation-agnostic:
- primaryAxisGroupByFieldMetadataId = main grouping field (e.g.,
"Company Name")
- secondaryAxisGroupByFieldMetadataId = optional secondary grouping
(e.g., "Stage")
These fields have consistent meaning regardless of orientation. The
visual mapping happens at the UI layer:
- Vertical bars: primary data renders on X-axis, secondary on Y-axis
- Horizontal bars: primary data renders on Y-axis, secondary on X-axis
Both chart types share the same DTO structure with consistent
nullability.
### What Changed
- Split GraphType.BAR → VERTICAL_BAR | HORIZONTAL_BAR
- Renamed fields: primaryAxisGroupByFieldMetadataId,
secondaryAxisGroupByFieldMetadataId (+ subfield
variants)
- useChartSettingsValues(): Maps semantic fields to setting values (no
swapping)
- getBarChartSettings(): Dynamically arranges settings panel based on
orientation
- transformGroupByDataToBarChartData(): Maps semantic fields to Nivo's
layout prop
video QA
https://github.com/user-attachments/assets/479061b5-712e-4ca6-9858-95273d1f16c1
This PR is the first part of the creation of the Chart editor.
https://github.com/user-attachments/assets/8b0af8ea-be41-4506-84cb-e40f5521cbf0
Done:
- Bar chart settings (except filters)
- Line chart settings (except filters)
In progress:
- Pie chart settings
- Number chart settings
- Gauge chart settings
Left to do:
- Loosen the backend validation to allow the user to save a partial
configuration, validate the graph configuration in the frontend and
display a error friendly message in the graph if the config is not
completed yet
- Implement the filter edition
- Finish the other graph types settings