## Summary
- Re-enable one lint rule that was temporarily disabled during the
ESLint-to-Oxlint migration:
- **`twenty/sort-css-properties-alphabetically`** in twenty-front — 578
violations auto-fixed across 390 files
- Document why **`typescript/consistent-type-imports`** cannot be
auto-fixed in twenty-server: NestJS relies on `emitDecoratorMetadata`
for DI, so converting constructor parameter imports to `import type`
erases them at compile time and breaks dependency injection at runtime
- Right-size CI runners, reducing 8-core usage from 18 jobs to 3:
| Change | Jobs | Rationale |
|--------|------|-----------|
| **Keep 8-core** | `ci-merge-queue/e2e-test`,
`ci-front/front-sb-build`, `ci-front/front-build` | Heavy builds needing
max CPU + memory (10GB NODE_OPTIONS, full Storybook webpack bundling) |
| **8-core → 4-core** | `ci-server` (build, lint-typecheck, validation,
test, integration-test), `ci-front/front-sb-test`,
`ci-zapier/server-setup`, `ci-sdk/sdk-e2e-test` | Already sharded into
10-12 parallel instances, I/O-bound (DB/Redis), or moderate single
builds |
| **8-core → 2-core** | `ci-emails/emails-test` | Trivially lightweight
(build + curl health check) |
| **Removed** | `ci-front/front-chromatic-deployment` | Dead code —
permanently disabled with `if: false` |
- Fix merge queue CI issues:
- **Concurrency**: Use `merge_group.base_ref` instead of unique merge
group ref so new queue entries cancel previous runs
- **Required status checks**: Add `merge_group` trigger to all 6
required CI workflows (front, server, shared, website, docker-compose,
sdk) with `changed-files-check` auto-skipped for merge_group events —
status check jobs auto-pass without re-running full CI
- **Build caching**: Add Nx build cache restore/save to E2E test job
with fallback to `main` branch cache for faster frontend and server
builds
## Test plan
- [ ] CI passes on this PR (verifies lint rule auto-fix works)
- [ ] Verify 4-core runner jobs complete within their 30-minute timeouts
- [ ] Verify merge queue status checks auto-pass (ci-front-status-check,
ci-server-status-check, etc.)
- [ ] Verify merge queue E2E concurrency cancels previous runs when a
new PR enters the queue
## Summary
Completes the migration of the frontend styling system from **Emotion**
(`@emotion/styled`, `@emotion/react`) to **Linaria** (`@linaria/react`,
`@linaria/core`), a zero-runtime CSS-in-JS library where styles are
extracted at build time.
This is the final step of the migration — all ~494 files across
`twenty-front`, `twenty-ui`, `twenty-website`, and `twenty-sdk` are now
fully converted.
## Changes
### Styling Migration (across ~480 component files)
- Replaced all `@emotion/styled` imports with `@linaria/react`
- Converted runtime theme access patterns (`({ theme }) => theme.x.y`)
to build-time `themeCssVariables` CSS custom properties
- Replaced `useTheme()` hook (from Emotion) with
`useContext(ThemeContext)` where runtime theme values are still needed
(e.g., passing colors to non-CSS props like icon components)
- Removed `@emotion/react` `css` helper usages in favor of Linaria
template literals
### Dependency & Configuration Changes
- **Removed**: `@emotion/react`, `@emotion/styled` from root
`package.json`
- **Added**: `@wyw-in-js/babel-preset`, `next-with-linaria` (for
twenty-website SSR support)
- Updated Nx generator defaults from `@emotion/styled` to
`@linaria/react` in `nx.json`
- Simplified `vite.config.ts` (removed Emotion-specific configuration)
- Updated `twenty-website/next.config.js` to use `next-with-linaria` for
SSR Linaria support
### Storybook & Testing
- Removed `ThemeProvider` from Emotion in Storybook previews
(`twenty-front`, `twenty-sdk`)
- Now relies solely on `ThemeContextProvider` for theme injection
### Documentation
- Removed the temporary `docs/emotion-to-linaria-migration-plan.md`
(migration complete)
- Updated `CLAUDE.md` and `README.md` to reflect Linaria as the styling
stack
- Updated frontend style guide docs across all locales
## How it works
Linaria extracts styles at build time via the `@wyw-in-js/vite` plugin.
All expressions in `styled` template literals must be **statically
evaluable** — no runtime theme objects or closures over component state.
- **Static styles** use `themeCssVariables` which map to CSS custom
properties (`var(--theme-color-x)`)
- **Runtime theme access** (for non-CSS use cases like icon `color`
props) uses `useContext(ThemeContext)` instead of Emotion's `useTheme()`
## 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.)
Upgraded from 8.6.15 to 9.1.17 in two steps:
- 8.6.15 -> 9.0.0
- 9.0.0 -> 9.1.17
I had to disable `storybook-addon-cookie` since it is not supported for
Storybook 9. However, I do intend to upgrade to Storybook 10 when this
is merged, so we can replace the aforementioned add-on with this fork
specifically created to support Storybook 10 and above:
https://www.npmjs.com/package/@storybook-community/storybook-addon-cookie.
Additionally, once we upgrade to Version 10 successfully, I will start
looking into integrating the official Vitest add-on.
## Summary
This PR enforces the use of `@/` alias for imports instead of relative
parent imports (`../`).
## Changes
### ESLint Configuration
- Added `no-restricted-imports` pattern in `eslint.config.react.mjs` to
block `../*` imports with the message "Relative parent imports are not
allowed. Use @/ alias instead."
- Removed the non-working `import/no-relative-parent-imports` rule
(doesn't work properly in ESLint flat config)
### VS Code Settings
- Added `javascript.preferences.importModuleSpecifier: non-relative` to
`.vscode/settings.json` (TypeScript setting was already there)
### Code Fixes
- Fixed **941 relative parent imports** across **706 files** in
`packages/twenty-front`
- All `../` imports converted to use `@/` alias
## Why
- Consistent import style across the codebase
- Easier to move files without breaking imports
- Better IDE support for auto-imports
- Clearer understanding of where imports come from
## Summary
This PR adds the `lingui/no-unlocalized-strings` ESLint rule to detect
untranslated strings and fixes translation issues across multiple
components.
## Changes
### ESLint Configuration (`eslint.config.react.mjs`)
- Added comprehensive `ignore` patterns for non-translatable strings
(CSS values, HTML attributes, technical identifiers)
- Added `ignoreNames` for props that don't need translation (className,
data-*, aria-*, etc.)
- Added `ignoreFunctions` for console methods, URL APIs, and other
non-user-facing functions
- Disabled rule for debug files, storybook, and test files
### Components Fixed (~19 files)
- Object record components (field inputs, pickers, merge dialogs)
- Settings components (accounts, admin panel)
- Serverless function components
- Record table and title cell components
## Status
🚧 **Work in Progress** - ~124 files remaining to fix
This PR is being submitted as draft to allow progressive fixing of
remaining translation issues.
## Testing
- Run `npx eslint "src/**/*.tsx"` in `packages/twenty-front` to check
remaining issues
Closes https://github.com/twentyhq/core-team-issues/issues/1560
If viewId is defined in a groupBy query, we want to apply all filters of
the view to the query.
This required to move a lot of code from twenty-front to twenty-shared
to convert the filters as stored in the db into graphql filters,
applying the right combinations between filters etc., which was
previously only done in the FE.
This PR does not handle any field filters, it will be done in a later pr
## Problem
**CRITICAL:** Two PRs were accidentally reverted when PR #14347 "Prevent
csv export injections" was merged:
1. **PR #14348** "[Page Layout] - Review Refactor" - ✅ **RESTORED**
2. **PR #14352** "Fix wrong path used by backend" - ✅ **RESTORED**
## Root Cause Analysis
During the merge of PR #14347, there was a complex merge conflict with
PR #14352 "Fix wrong path used by backend". The merge commit
`324d7204bb` in the PR #14347 branch brought in changes from PR #14352,
but during the conflict resolution, **BOTH PR #14348 and PR #14352's
changes were accidentally overwritten**.
## What This PR Restores
This PR restores **BOTH** PRs by cherry-picking their commits:
### ✅ PR #14348 Changes Restored:
- `GraphWidgetRenderer.tsx` - was deleted, now restored
- `WidgetRenderer.tsx` - was missing, now restored
- `SettingsPageLayoutTabsInstanceId.ts` - was deleted, now restored
- `useUpdatePageLayoutWidget.ts` - was renamed back, now restored with
correct name
- Multiple test files that were deleted
- Several hook files that were renamed/reverted
- File renames: `usePageLayoutWidgetUpdate.ts` →
`useUpdatePageLayoutWidget.ts`
- Hook refactoring and test file organization
- Page layout component improvements
### ✅ PR #14352 Changes Restored:
- **Types moved to twenty-shared:**
- `packages/twenty-shared/src/types/AppBasePath.ts` ✅ RESTORED
- `packages/twenty-shared/src/types/AppPath.ts` ✅ RESTORED
- `packages/twenty-shared/src/types/SettingsPath.ts` ✅ RESTORED
- **Navigation utilities moved to twenty-shared:**
- `packages/twenty-shared/src/utils/navigation/getAppPath.ts` ✅ RESTORED
- `packages/twenty-shared/src/utils/navigation/getSettingsPath.ts` ✅
RESTORED
- **200+ import statements updated** across the codebase to use
twenty-shared
- **Old type files deleted** from twenty-front/src/modules/types/
## Evidence of Complete Restoration
**Before (reverted state):**
- ❌ Types were in `packages/twenty-front/src/modules/types/`
- ❌ Page layout files missing
- ❌ Hook files incorrectly named
**After (this PR):**
- ✅ Types correctly in `packages/twenty-shared/src/types/`
- ✅ All page layout files restored
- ✅ Hook files correctly named
- ✅ All import statements updated
## Verification
**Total changes:**
- PR #14348: 36 files changed, 863 insertions(+), 442 deletions(-)
- PR #14352: 243 files changed, 492 insertions(+), 461 deletions(-)
- **Combined: 279 files changed, 1355 insertions(+), 903 deletions(-)**
## Impact
This completely restores both PRs that were accidentally lost, ensuring:
1. Page layout refactoring work is back
2. Type organization and path utilities are correctly in twenty-shared
3. Backend email paths work correctly again
4. No functionality is lost
Fixes the reversion caused by the merge conflict in PR #14347.
---------
Co-authored-by: nitin <142569587+ehconitin@users.noreply.github.com>
**Small Security Issue:** CSV exports were vulnerable to formula
injection attacks when users entered values starting with =, +, -, or @.
(only happens if a logged-in user injects corrupted data)
Solution:
- Added ZWJ (Zero-Width Joiner) protection that prefixes dangerous
values with invisible Unicode character
- This is the best way to preserve original data while preventing Excel
from executing formulas
- Added import cleanup to restore original values when re-importing
Changes:
- New sanitizeValueForCSVExport() function for security
- Updated all CSV export paths to use both security + formatting
functions
- Added comprehensive tests covering attack vectors and international
characters
- Also added cursor rules for better code consistency
---------
Co-authored-by: Charles Bochet <charlesBochet@users.noreply.github.com>
After moving a section on the frontend, this broke the path that was
sent by email on the backend.
This kind of error comes back every ~2-3 month under different forms so
we need a more robust solution: I moved routes to the shared folder,
that way we will share one common source of truth between the frontend
and the backend.
Fixes#14343
This PR removes any V2 naming in state management logic and some minor
utils and hooks.
It has a lot of changes but nearly all of them were made by the rename
functionality of vscode which is deterministic, so it shouldn't
introduce any regression.
QA has been made on this PR on the main features of the app without any
noticeable issue.
Also renamed some other v2 naming related items :
- TextInputV2 => TextInput
- TextInput => SettingsTextInput
- ObjectFilterDropdownFilterSelectMenuItemV2 =>
ObjectFilterDropdownFilterSelectMenuItem
- useInitDraftValueV2 => useInitDraftValue
- useOpenRecordTableCellV2 => useOpenRecordTableCell
This PR removes every legacy state management logic left in the code
base, we only keep our last component state management logic.
Removed :
- Scoped states logic
- Dropdown scope logic
- Scope id naming
- Component state v1
- Component state v2_alpha
# QA
| Component | Comments |
| --- | --- |
| AttachementDropdown | Ok |
| ObjectTasks | Ok |
| ActivityRichTextEditor | Ok |
| App | Ok dialogs work |
| AppRouterProviders | Ok dialogs work |
| RecordBoardScrollToFocusedCardEffect | Ok |
| RecordBoard | Ok |
| RecordBoardCard | OK |
| useSelectAllCards | Ok |
| RecordInlineCell | Ok |
| useInlineCell | Ok |
| RecordDetailRelationRecordsListItem | Ok |
| RecordDetailRelationSectionDropdownToMany | Ok |
| RecordDetailRelationSectionDropdownToOne | Ok |
| useHandleContainerMouseEnter | Ok |
| useResetTableRowSelection | Ok |
| useSelectAllRows | Ok |
| useSetRecordTableData | Ok |
| useRecordTable | Ok |
| useRecordTableMoveFocusedCell | Ok |
| useRecordTableMoveFocusedRow | Ok |
| useMoveHoverToCurrentCell | OK |
| useTriggerActionMenuDropdown | Ok |
| RecordTableBodyDragDropContextProvider | Ok |
| RecordTableHeaderCell | Ok |
| useSetCurrentRowSelected | Ok |
| useRecordTitleCell | Ok |
| SettingsObjectFieldActiveActionDropdown | Ok |
| SettingsObjectFieldInactiveActionDropdown | Ok |
| SettingsObjectFieldItemTableRow | Ok |
| SettingsObjectInactiveMenuDropDown | Ok |
| SettingsRoleEditEffect | Ok |
| SettingsRolesQueryEffect | Ok |
| Dropdown | Ok |
| useSelectableList | Ok |
| useSelectableListHotKeys | Ok |
| useSelectableListListenToEnterHotkeyOnItem | Ok |
| useNavigationSection | Ok |
| useCreateViewFromCurrentState | Ok |
| useDeleteViewFromCurrentState | Ok |
| useUpdateViewFromCurrentState | Ok |
| ViewPickerFavoriteFoldersDropdown | Ok |
| useCreateViewFromCurrentView | Ok |
| ViewBarDetails | Ok |
| WorkflowRunVisualizer | Ok |
| useHandleWorkflowRunDiagramCanvasInit | Ok |
| WorkflowDiagramCanvasBase | Ok |
| WorkflowRunVisualizerEffect | Ok |
| useRemoveStepFilter | Ok |
| useRemoveStepFilterGroup | Ok |
| useUpsertStepFilterSettings | Ok |
| WorkflowFindRecordsFilters | Ok |
| SettingsObjects | Ok |
Webhooks are still documented in core playground to detail Webhooks of
core objects.
We moved webhooks to metadata playground and forgot to keep computation
of WebhookForResponse
This PR adds it back
This PR aims at improving readability in sentry and user experience with
runtime errors.
**GraphQL errors (and ApolloError)**
1. In sentry we have a lot of "Object captured as exception with keys:
extensions, message" errors (2k over the last 90d), on which we have
zero information. This is because in apollo-factory we were passing on
GraphQL errors to sentry directly why sentry expects the structure of a
JS Error. We are now changing that, rebuilding an Error object and
attempting to help grouping by creating a fingerPrint based on error
code and truncated operationName (same as we do in the back for 500
graphql errors).
2. In sentry we have a lot of ApolloError, who actually correspond to
errors that should not be logged in sentry (Forbidden errors such as
"Email is not verified"), or errors that are already tracked by back-end
(Postgres errors such as "column xxx does not exist"). This is because
ApolloErrors become unhandled rejections errors if they are not caught
and automatically sent to sentry through the basic config. To change
that we are now filtering out ApolloErrors created from GraphQL Errors
before sending error to sentry:
<img width="524" alt="image"
src="https://github.com/user-attachments/assets/02974829-26d9-4a9e-8c4c-cfe70155e4ab"
/>
**Runtime errors**
4. Runtime errors were all caught by sentry with the name "Error",
making them not easy to differentiate on sentry (they were not grouped
together but all appeared in the list as "Error"). We are replacing the
"Error" name with the error message, or the error code if present. We
are introducing a CustomError class that allows errors whose message
contain dynamic text (an id for instance) to be identified on sentry
with a common code. _(TODO: if this approach is validated then I have
yet to replace Error with dynamic error messages with CustomError)_
5. Runtime error messages contain technical details that do not mean
anything to users (for instance, "Invalid folder ID: ${droppableId}",
"ObjectMetadataItem not found", etc.). Let's replace them with "Please
refresh the page." to users and keep the message error for sentry and
our dev experience (they will still show in the console as uncaught
errors).
Fixes#11762
The `copy-button` classname from scalar was overwritting the style of
our copy button. It only happened in production build and not with `npx
nx run twenty-front:start` so it was quite hard to catch. Thanks
@charlesBochet for finding the root cause.
- Removed `copy-button` classname since it was unused
- Added `?inline` when importing scalar css to have inline css
Before:
<img width="210" alt="Capture d’écran 2025-05-16 à 17 43 50"
src="https://github.com/user-attachments/assets/7256b5e6-0b61-4590-a4de-d6b28ab2b4ed"
/>
After:
<img width="230" alt="Capture d’écran 2025-05-16 à 17 43 32"
src="https://github.com/user-attachments/assets/a763172b-0566-413d-bbdd-470f28097ae4"
/>
We want to have fewer base path for routing.
We will have:
- /files
- /webhooks
- /graphql
- /metadata
- /rest
- /auth
- /healthz
I'm moving /open-api under /rest, and centralizing the webhooks
(removing /stripe and /cloudflare)
This is a minor rework of PR #10738.
I noticed an inconsistency with how Select options are passed as props.
Many files use constants stored in external files to pass options props
to Select objects. This allows for code reusability. Some files are not
passing options in this format.
I modified more files so that they use this method of passing options
props. I made changes to:
- WorkerQueueMetricsSection.tsx
- SettingsDataModelFieldBooleanForm.tsx
- SettingsDataModelFieldTextForm.tsx
- SettingsDataModelFieldNumberForm.tsx
- PlaygroundSetupForm.tsx
- ViewPickerContentCreateMode.tsx
I also noticed that some of these files were incorrectly using
useLingui(), so I fixed the import and usage where needed.
---------
Co-authored-by: Beau Smith <bsmith26@iastate.edu>
Co-authored-by: Charles Bochet <charles@twenty.com>
# 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
CSS was loaded in a global context (full screen which might be re-used
for other use cases in the future) instead of a local context.
\+ small update on .env.example