## Introduction
In aim to reduce and optimize the number of twenty-front build we do
during our cd process and allow twenty-front build promotion
### Build time
**Nothing is baked.** The `build/` directory is a clean, env-agnostic
artifact. `index.html` contains the empty placeholder:
```html
<script id="twenty-env-config">
window._env_ = {
// This will be overwritten
};
</script>
```
The JS bundles contain no hardcoded server URL.
---
### Deploy mode 1: Frontend served by the backend (Docker / NestJS)
1. Container starts, NestJS boots in `main.ts`
2. `generateFrontConfig()` runs, reads `process.env.SERVER_URL`
3. Rewrites `dist/front/index.html`, replacing the placeholder with:
```html
<script id="twenty-env-config">
window._env_ = {
REACT_APP_SERVER_BASE_URL: "https://api.example.com"
};
</script>
```
4. NestJS serves the static `dist/front/` directory
5. Browser loads `index.html`, `window._env_` is set before the app JS
executes
6. `src/config/index.ts` reads `window._env_.REACT_APP_SERVER_BASE_URL`
and uses it
---
### Deploy mode 2: Frontend served standalone (CDN / nginx / static
server)
1. Take the `build/` artifact as-is
2. Before serving, run at deploy time:
```bash
REACT_APP_SERVER_BASE_URL=https://api.example.com sh
./scripts/inject-runtime-env.sh
```
3. This does the same `sed` replacement on `build/index.html`
4. Serve the `build/` directory with your static server of choice
5. Same resolution in the browser:
`window._env_.REACT_APP_SERVER_BASE_URL` is picked up by
`src/config/index.ts`
---
### Fallback: no injection at all
If neither mechanism runs (e.g. local dev with `vite dev`),
`window._env_.REACT_APP_SERVER_BASE_URL` is `undefined`, and
`getDefaultUrl()` kicks in:
- **Localhost**: returns `http://localhost:3000`
- **Non-localhost**: returns same-origin (`window.location.origin`)
## Summary
Splits admin-panel resolvers off the shared `/metadata` GraphQL endpoint
onto a dedicated `/admin-panel` endpoint. The backend plumbing mirrors
the existing `metadata` / `core` pattern (new scope, decorator, module,
factory), and admin types now live in their own
`generated-admin/graphql.ts` on the frontend — dropping 877 lines of
admin noise from `generated-metadata`.
## Why
- **Smaller attack surface on `/metadata`** — every authenticated user
hits that endpoint; admin ops don't belong there.
- **Independent complexity limits and monitoring** per endpoint.
- **Cleaner module boundaries** — admin is a cross-cutting concern that
doesn't match the "shared-schema configuration" meaning of `/metadata`.
- **Deploy / blast-radius isolation** — a broken admin query can't
affect `/metadata`.
Runtime behavior, auth, and authorization are unchanged — this is a
relocation, not a re-permissioning. All existing guards
(`WorkspaceAuthGuard`, `UserAuthGuard`,
`SettingsPermissionGuard(SECURITY)` at class level; `AdminPanelGuard` /
`ServerLevelImpersonateGuard` at method level) remain on
`AdminPanelResolver`.
## What changed
### Backend
- `@AdminResolver()` decorator with scope `'admin'`, naming parallels
`CoreResolver` / `MetadataResolver`.
- `AdminPanelGraphQLApiModule` + `adminPanelModuleFactory` registered at
`/admin-panel`, same Yoga hook set as the metadata factory (Sentry
tracing, error handler, introspection-disabling in prod, complexity
validation).
- Middleware chain on `/admin-panel` is identical to `/metadata`.
- `@nestjs/graphql` patch extended: `resolverSchemaScope?: 'core' |
'metadata' | 'admin'`.
- `AdminPanelResolver` class decorator swapped from
`@MetadataResolver()` to `@AdminResolver()` — no other changes.
### Frontend
- `codegen-admin.cjs` → `src/generated-admin/graphql.ts` (982 lines).
- `codegen-metadata.cjs` excludes admin paths; metadata file shrinks by
877 lines.
- `ApolloAdminProvider` / `useApolloAdminClient` follow the existing
`ApolloCoreProvider` / `useApolloCoreClient` pattern, wired inside
`AppRouterProviders` alongside the core provider.
- 37 admin consumer files migrated: imports switched to
`~/generated-admin/graphql` and `client: useApolloAdminClient()` is
passed to `useQuery` / `useMutation`.
- Three files intentionally kept on `generated-metadata` because they
consume non-admin Documents: `useHandleImpersonate.ts`,
`SettingsAdminApplicationRegistrationDangerZone.tsx`,
`SettingsAdminApplicationRegistrationGeneralToggles.tsx`.
### CI
- `ci-server.yaml` runs all three `graphql:generate` configurations and
diff-checks all three generated dirs.
## Authorization (unchanged, but audited while reviewing)
Every one of the 38 methods on `AdminPanelResolver` has a method-level
guard:
- `AdminPanelGuard` (32 methods) — requires `canAccessFullAdminPanel ===
true`
- `ServerLevelImpersonateGuard` (6 methods: user/workspace lookup + chat
thread views) — requires `canImpersonate === true`
On top of the class-level guards above. No resolver method is accessible
without these flags + `SECURITY` permission in the workspace.
## Test plan
- [ ] Dev server boots; `/graphql`, `/metadata`, `/admin-panel` all
mapped as separate GraphQL routes (confirmed locally during
development).
- [ ] `nx typecheck twenty-server` passes.
- [ ] `nx typecheck twenty-front` passes.
- [ ] `nx lint:diff-with-main twenty-server` and `twenty-front` both
clean.
- [ ] Manual smoke test: log in with a user who has
`canAccessFullAdminPanel=true`, open the admin panel at
`/settings/admin-panel`, verify each tab loads (General, Health, Config
variables, AI, Apps, Workspace details, User details, chat threads).
- [ ] Manual smoke test: log in with a user who has
`canImpersonate=false` and `canAccessFullAdminPanel=false`, hit
`/admin-panel` directly with a raw GraphQL request, confirm permission
error on every operation.
- [ ] Production deploy note: reverse proxy / ingress must route the new
`/admin-panel` path to the Nest server. If the proxy has an explicit
allowlist, infra change required before cutover.
## Follow-ups (out of scope here)
- Consider cutting over the three
`SettingsAdminApplicationRegistration*` components to admin-scope
versions of the app-registration operations so the admin page is fully
on the admin endpoint.
- The `renderGraphiQL` double-assignment in
`admin-panel.module-factory.ts` is copied from
`metadata.module-factory.ts` — worth cleaning up in both.
## Summary
- **Reduce app-dev image size** by stripping ~60MB of build artifacts
not needed at runtime from the server build stage: `.js.map` source maps
(29MB), `.d.ts` type declarations (9MB), compiled test files (14MB), and
unused package source directories (~9MB).
- **Add CI smoke test** for the `twenty-app-dev` all-in-one Docker
image, running in parallel with the existing docker-compose test. Builds
the image, starts the container, and verifies `/healthz` returns 200.
## Test plan
- [x] Built image locally and verified server, worker, Postgres, and
Redis all start correctly
- [x] Verified `/healthz` returns 200 and frontend serves at `/`
- [ ] CI `test-compose` job passes (existing test, renamed from `test`)
- [ ] CI `test-app-dev` job passes (new parallel job)
Made with [Cursor](https://cursor.com)
This PR:
- Breaks useAgentChatData into focused effect components (streaming,
fetch,
init, auto-scroll, diff sync)
- Splits message list into non-last (stable) + last (streaming/error) to
prevent full re-renders on each stream chunk
- Adds scroll-to-bottom button and MutationObserver-based auto-scroll on
thread switch
- Lifts loading state from context to atoms
- Adds areEqual to selector
factories
We could improve further but this sets up a robust architecture for
further refactoring.
## Messages flow
The flow of messages loading and streaming is now more solid.
Everything goes out from `AgentChatAiSdkStreamEffect`, whether loaded
from the DB or streaming directly, and every consumers is using only one
atom `agentChatMessagesComponentFamilyState`
## Data sync effect with callbacks new hook
See
`packages/twenty-front/src/modules/apollo/hooks/useQueryWithCallbacks.ts`
which allows to fix Apollo v4 migration leftovers and is an
implementation of the pattern we talked about with @charlesBochet
We could refine this pattern in another PR.
# Before
https://github.com/user-attachments/assets/84e7a96f-6790-405d-8a73-2dacbf783be5
# After
https://github.com/user-attachments/assets/4c692e3a-2413-4513-abcc-44d0da311203
Co-authored-by: Charles Bochet <charles@twenty.com>
## Summary
Consolidates `objectMetadataItems` onto the metadata store as the
**single source of truth**, replacing the previous dual-store approach
(separate `objectMetadataItemsState` atom + untyped
`metadataStoreState`).
### Architecture: three-layer design
```
┌─────────────────────────────────────────────────────────┐
│ Store Layer (granular, typed) │
│ objectMetadataItems → FlatObjectMetadataItem[] │
│ fieldMetadataItems → FlatFieldMetadataItem[] │
│ indexMetadataItems → FlatIndexMetadataItem[] │
└────────────────┬────────────────────────────────────────┘
│ .current (never draft)
┌────────────────▼────────────────────────────────────────┐
│ Selectors (typed read-only) │
│ objectMetadataItemsSelector │
│ fieldMetadataItemsSelector │
│ indexMetadataItemsSelector │
│ metadataStoreStatusFamilySelector │
│ isSystemObjectByNameSingularFamilySelector (narrow) │
│ activeObjectNameSingularsSelector (narrow) │
└────────────────┬────────────────────────────────────────┘
│ joins objects + fields + indexes + permissions
┌────────────────▼────────────────────────────────────────┐
│ Joining Selector │
│ objectMetadataItemsWithFieldsSelector │
│ → produces full ObjectMetadataItem[] with │
│ readableFields / updatableFields from permissions │
│ → 12 existing selectors repointed here │
└─────────────────────────────────────────────────────────┘
```
### Key changes
- **Granular flat types** (`FlatObjectMetadataItem`,
`FlatFieldMetadataItem`, `FlatIndexMetadataItem`) — objects stored
without embedded fields/indexes, matching backend "Flat" naming
convention
- **Typed write API** — `updateDraft` is now generic via
`MetadataEntityTypeMap`, giving compile-time safety on what data shape
goes to each key
- **Write path refactored** — fetch → split into flat entities via
`splitObjectMetadataItemWithRelated` → write to metadata store directly.
No more dual-write through `objectMetadataItemsState`. Permissions
enrichment moved from write path into the joining selector.
- **SSE effects write directly** — `ObjectMetadataItemSSEEffect` and
`FieldMetadataSSEEffect` now patch the store from the SSE event payload
(create/update/delete) instead of triggering a full re-fetch
- **`objectMetadataItemsState` bridge** — converted from writable
`createAtomState` to read-only `createAtomSelector` that delegates to
the joining selector. All 100+ existing consumers continue to work
without code changes.
- **All selectors use Twenty state API** — `createAtomSelector` /
`createAtomFamilySelector` throughout, no raw `atom()`
- **Narrow selectors** for hot paths —
`isSystemObjectByNameSingularFamilySelector` and
`activeObjectNameSingularsSelector` read from flat objects only,
avoiding re-renders when fields/indexes/permissions change. Placed in
`object-metadata/states/` as higher-level business selectors.
- **Test helper** — `setTestObjectMetadataItemsInMetadataStore` for
tests that need to set up composite object metadata through the store
(clearly named as a testing utility)
### Naming conventions
- `ObjectMetadataItemWithRelated` — type for objects with embedded
fields/indexes (input to split utility)
- `FlatObjectMetadataItem` / `FlatFieldMetadataItem` /
`FlatIndexMetadataItem` — granular store types
- Selector names don't expose "Current" — that's an internal detail of
the metadata store API
### Future work
- Optimistic update API (`updateCurrentOptimistically` with rollback)
- Migrate remaining entities (views, pageLayouts, etc.) to the same
pattern
- Gradually remove `objectMetadataItemsState` bridge once all direct
imports are replaced
## Test plan
- [x] `npx nx typecheck twenty-front` passes
- [x] `npx nx lint:diff-with-main twenty-front` passes
- [ ] Verify app loads correctly with metadata from the store
- [ ] Verify SSE updates (object/field changes) propagate correctly
- [ ] Run existing test suites to confirm no regressions
## Summary
Front Before:
<img width="1199" height="670" alt="image"
src="https://github.com/user-attachments/assets/b978f67c-c0a6-49fc-bedd-a443f11c365d"
/>
Front After:
<img width="1199" height="670" alt="image"
src="https://github.com/user-attachments/assets/a4939dbb-a8b4-4c74-978c-daa7f27d00f3"
/>
Server Before:
<img width="1199" height="670" alt="image"
src="https://github.com/user-attachments/assets/da53e97f-ec65-4224-a656-ca41040aef6e"
/>
Server After:
<img width="1199" height="670" alt="image"
src="https://github.com/user-attachments/assets/8cdf3885-f515-4d6c-989f-a421a4e8206c"
/>
### CI Server Pipeline Restructuring
- Split monolithic `server-setup` job into three parallel jobs:
`server-build`, `server-lint-typecheck`, and `server-validation`
- `server-build` only handles build + Nx cache save (~1m vs old 3.5m),
unblocking downstream jobs faster
- `server-lint-typecheck` runs in parallel with no DB dependency
- `server-validation` handles DB setup, migration checks, and GraphQL
generation checks in parallel with tests
- Make `server-test` (unit tests) fully independent — no longer waits
for server-setup, builds its own artifacts
- Increase integration test shards from 8 to 10 for better parallelism
- Expected critical path reduction: ~10m → ~7m (~30% faster)
### CI Front Pipeline Improvements
- Use artifact upload/download for storybook build instead of rebuilding
in test shards
- Serve pre-built storybook via `http-server` in test jobs, with
`STORYBOOK_URL` env var
- Update `vitest.config.ts` to use `storybookUrl` when `STORYBOOK_URL`
is set
- Remove redundant `twenty-shared`, `twenty-ui`, `twenty-sdk` builds
from storybook test shards
### Vite Build Optimizations
- Conditionally enable `rollup-plugin-visualizer` behind `ANALYZE=true`
env var (not loaded by default)
- Broaden Istanbul coverage exclusions to skip test files, stories,
mocks, and decorators
- Remove `@tabler/icons-react` alias from twenty-front and storybook
configs
- Bundle `@tabler/icons-react` into twenty-ui instead of treating it as
an external dependency
- Add lazy loading with `React.lazy` + `Suspense` for all page-level
route components in `useCreateAppRouter`
## Summary
Removes `vite-plugin-checker` and all references to
`VITE_DISABLE_TYPESCRIPT_CHECKER` / `VITE_DISABLE_ESLINT_CHECKER`.
These background checks are no longer needed because our dev experience
now relies on **independent** linters and type-checkers:
- `npx nx lint:diff-with-main twenty-front` for ESLint
- `npx nx typecheck twenty-front` for TypeScript
Running these as separate processes (rather than inside Vite) is faster,
gives cleaner output, and avoids the significant memory overhead that
`vite-plugin-checker` introduces during `vite dev` and `vite build`. The
old env vars to disable them are removed from `vite.config.ts`,
`package.json` scripts, `nx.json`, `.env.example`, and all translated
docs.
## Summary
- Move Modal UI components (`Modal`, `ModalContent`, `ModalHeader`,
`ModalFooter`, `ModalBackdrop`) from `twenty-front` to `twenty-ui` as
stateless, reusable components
- Create `ModalStatefulWrapper` in `twenty-front` that connects Jotai
state (`isModalOpenedComponentState`) to the stateless `Modal` via an
`isOpen` prop
- Rename `modalVariant` prop to `overlay` with clearer values: `'dark'`
(default), `'light'` (in-container), `'transparent'` (invisible panel).
Remove unused `'medium'` overlay
- Rename `modalId` to `modalInstanceId` across the entire modal zone
(~30 consumer files)
- Extract `ModalProps` to its own file in
`twenty-ui/types/ModalProps.ts`; extract `ModalStatefulWrapperProps` to
its own file using `Pick<ModalProps, ...>` for shared props
- Extract `ModalBackdrop` to its own file and export from `twenty-ui`;
use it in `UserOrMetadataLoader` instead of a local styled component
- Use `ModalFooter` in `StepNavigationButton` and `ModalHeader` in
`SpreadsheetImportStepperContainer` instead of duplicated `styled.div`
definitions
- Remove unused `onClose` prop from stateless `Modal`; fix `typeof
document` guard in `ModalStatefulWrapper`
- Split shared types into individual files: `ModalSize.ts`,
`ModalPadding.ts`, `ModalOverlay.ts`
- Extract wyw profiling instrumentation from `vite.config.ts` into
reusable `createWywProfilingPlugin` with parametrized threshold and
improved logging
- Delete old `Modal.tsx`, `Modal.styles.ts`, `ModalContent.tsx`,
`ModalHeader.tsx`, `ModalFooter.tsx` from `twenty-front`
- Add comprehensive Storybook stories in `twenty-ui` covering Default,
Confirmation, Small, ExtraLarge, Closed, and Interactive variants
## 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()`
## 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;
```
This PR handles SSE event stream edge cases.
- When a pod restarts, the front clients have to reconnect to SSE
- When the dev server restarts or is hot reloaded, the front client has
to reconnect to SSE
- When redis server restarts or the redis key is cleared for any reason,
the server has to recreate the event stream in redis, this can happen
when navigating for example.
- Log in / log out flow
With this PR we avoid error messages in the front end due to TTL or pod
crash, we implement a resilient way of reconnecting silently.
To avoid DDoSing our servers if pods crash or a full restart of the
cluster is made, we evenly space retry attempts to reconnect from all
the clients, to avoid n clients reconnection at the same time, we use a
random wait time between 0 and a constant max wait time (set to 2 mins
for now). This is the cheapest and most effective solution, clients who
want to force reconnect have to refresh or navigate to another page.
Fixes https://github.com/twentyhq/core-team-issues/issues/2045
## Summary
This PR fixes the `tsconfig` setup in `twenty-front` so that `tsgo -p
tsconfig.json` properly type-checks all files.
### Root Cause
The previous setup used TypeScript project references with `files: []`
in the main `tsconfig.json`. When running `tsgo -p tsconfig.json`, this
checks nothing because `tsgo` requires the `-b` (build) flag for project
references, but the configs weren't set up for composite mode.
### Changes
**Simplified tsconfig architecture (4 files → 2):**
- `tsconfig.json` - All files (dev, tests, stories) for
typecheck/IDE/lint
- `tsconfig.build.json` - Production files only (excludes tests/stories)
**Removed redundant configs:**
- `tsconfig.dev.json`
- `tsconfig.spec.json`
- `tsconfig.storybook.json`
**Updated references:**
- `jest.config.mjs` → uses `tsconfig.json`
- `eslint.config.mjs` → uses `tsconfig.json`
- `vite.config.ts` → uses `tsconfig.json` for dev
**Type fixes (pre-existing errors revealed by proper typechecking):**
- Made `applicationId` optional in `FieldMetadataItem` and
`ObjectMetadataItem`
- Added missing `navigationMenuItem` translation
- Added `objectLabelSingular` to Search GraphQL query
- Fixed `sortMorphItems.test.ts` mock data
## Test plan
- [ ] Run `npx nx typecheck twenty-front` - should pass
- [ ] Run `npx nx lint twenty-front` - should work
- [ ] Run `npx nx test twenty-front` - should work
- [ ] Run `npx nx build twenty-front` - should work
- [ ] Verify IDE type checking works correctly
The `VITE_DISABLE_ESLINT_CHECKER` environment variable is removed from
the codebase. ESLint checker no longer runs during Vite builds
(equivalent to the previous `VITE_DISABLE_ESLINT_CHECKER=true`
behavior).
**Configuration**
- Removed from `.env.example` (active and commented lines)
- Removed from `vite.config.ts` destructuring and conditional logic
- Removed from build scripts in `package.json` and `nx.json`
**Code change in vite.config.ts:**
```diff
- if (VITE_DISABLE_ESLINT_CHECKER !== 'true') {
- checkers['eslint'] = {
- lintCommand: 'eslint ../../packages/twenty-front --max-warnings 0',
- useFlatConfig: true,
- };
- }
```
**Documentation**
- Updated main English troubleshooting guide to remove references to the
variable
- Translated documentation files are intentionally not modified and will
be handled by a separate workflow
> [!NOTE]
> ESLint will not run in the background via Vite's checker plugin.
Developers will need to run `npx nx lint twenty-front` manually or rely
on their IDE's ESLint extension for real-time feedback on open files.
Created from VS Code via the <a
href="https://marketplace.visualstudio.com/items?itemName=GitHub.vscode-pull-request-github">GitHub
Pull Request</a> extension.
<!-- START COPILOT CODING AGENT SUFFIX -->
<details>
<summary>Original prompt</summary>
> Your job is to delete everything related to
VITE_DISABLE_ESLINT_CHECKER in the codebase.
>
> We mention this env var in documentation: drop the content talking
about it.
>
> We use it in the vite config: drop the check and keep the code paths
that used to run when `VITE_DISABLE_ESLINT_CHECKER=true`.
>
> User has selected text in file packages/twenty-front/.env.example from
3:1 to 3:28
</details>
Created from VS Code via the [GitHub Pull
Request](https://marketplace.visualstudio.com/items?itemName=GitHub.vscode-pull-request-github)
extension.
<!-- START COPILOT CODING AGENT TIPS -->
---
✨ Let Copilot coding agent [set things up for
you](https://github.com/twentyhq/twenty/issues/new?title=✨+Set+up+Copilot+instructions&body=Configure%20instructions%20for%20this%20repository%20as%20documented%20in%20%5BBest%20practices%20for%20Copilot%20coding%20agent%20in%20your%20repository%5D%28https://gh.io/copilot-coding-agent-tips%29%2E%0A%0A%3COnboard%20this%20repo%3E&assignees=copilot)
— coding agent works faster and does higher quality work when set up for
your repo.
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Devessier <29370468+Devessier@users.noreply.github.com>
In v1.8, we have already run a command to deprecate FULL or PARTIAL sync
stages.
However the code was fully deprecated in v1.10 and some workspaces might
still have this status used. This is to double check
This big PR implements table virtualization with an offset paging,
allowing a way more fluid UX.
It is a v1 that should be improved in the future with partial data
loading and optimization of the browser display performance of a row.
But with this PR we have the solid enough technical foundation, both
frontend and backend, to get to a smooth table UX.
Fixes and improvements after first successful round of development
(needed to have main clean) :
- [x] Delete should refresh virtualized portion only and reset all table
- [x] Fix add new : top and bottom
- [x] Table empty shouldn’t show when first loading
- [x] Fix d&d
- [x] Fix sorts
- [x] Fix drag when scrolling after a full virtual page (it throws an
error)
- [x] Si update mais qu’on a un sort ou filter, alors il faut trigger le
refresh
- [x] Reset scroll position between tables
- [x] Reset scroll shadows between tables
- [x] Setup d&n for virtual list :
https://github.com/hello-pangea/dnd/blob/main/docs/patterns/virtual-lists.md
- [x] Full table re-render when entering edit mode
- [x] Clean code and prepare for merge
Fixes https://github.com/twentyhq/core-team-issues/issues/1613 that
contains other bugs to be fixed before merge
---------
Co-authored-by: Charles Bochet <charles@twenty.com>
This PR refactors table resize with the new layout using flex-wrap to
manage all the table with divs.
Due to flex-wrap, we have to manually compute some filling divs like the
last column filler div or the empty table container, otherwise the
header can wrap and display on two lines, which is the only real issue
with this implementation with wrap.
Remember that **using flex-wrap is necessary** to handle z-index as we
want for the scrolling and hovered portal cell interaction UX. Otherwise
we lose the capacity to have z-index fine-tuning on very specific edge
cases.
To handle the resize, the most performant solution was to create CSS
variables for each column width on the table, creating a class for each
column, then assigning each class to all divs that are in the same
column, using the index of the array or the fixed known position index
for that.
This PR also refactors a lot of small details around the resizing, the
naming of the component and the DX.
It notably introduces a new hook : `useHTMLElementByIdWhenAvailable`,
that is very relies on MutationObserver API to get a native HTML element
as soon as it is rendered.
---------
Co-authored-by: Charles Bochet <charles@twenty.com>
BlocknoteJS requires an ESM module where our server is CJS, this forced
us to pin the server-util version, which led us to force the resolution
of several packages, leading to bugs downstream.
From Node 22.12 Node supports requiring ESM modules (available from Node
22.0 with a flag). So I upgrade the module.
I picked Node 22 and not Node 23 or Node 24 because 22 is the LTS and we
don't plan to change node versions frequently.
If you remain on Node 18, things should still mostly work, except if you
edit a Rich Text field.
I also starting changing the default runtime for Serverless Functions
which isn't directly related. This means new serverless functions will
be created on Node 22, but we will still need another PR to migrate
existing serverless functions before September (end of support by AWS).
(In this PR I also remove the upgrade commands from 0.43 since they rely
on Blocknote and I didn't want to have to deal with this)
---------
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Creating manual chunk was a bad idea, we should always solve lazy
loading problem at the source instance.
Setting a 4.5MB for the index bundle size, CI will fail if we go above.
There is still a lot of room for optimizations!
- More agressive lazy loading (e.g. xyflow and tiptap are still loaded
in index!)
- Add a prefetch mechanism
- Add stronger CI checks to make sure libraries we've set asides are not
added back
- Fix AllIcons component with does not work as intended (loaded on
initial load)
Chrome doesn't really respect preloading and was loading it before other
important assets, slowing down the app while in 99% of sessions people
don't check the REST API playground
1. Removing tokenPair internal variable of ApolloFactory. We will relay
on cookieStorage
2. setting the cookie explicitely instead of only relaying on recoil
cookieEffect which is too late
# 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
Lately encountering a lot of out of memory error when running
twenty-front in watch mode with both TypeScript and lint checkers
```ts
Error: Worker terminated due to reaching memory limit: JS heap out of memory
at new NodeError (node:internal/errors:405:5)
at [kOnExit] (node:internal/worker:287:26)
at Worker.<computed>.onexit (node:internal/worker:209:20)
```
The existing configuration looks like this:
```ts
// packages/twenty-front/vite.config.ts
'cd ../.. && eslint packages/twenty-front --report-unused-disable-directives --max-warnings 0 --config .eslintrc.cjs',
```
This is wrong as computing the root eslintrc completely omitting
twenty-front's one ***and its ignorePattern*** so will be checking in
`node_modules` etc checking for project-structure :).
For example this a
[snippet](https://gist.github.com/prastoin/d7f8ad4ef5eb2f7732209b756a38094c)
of the above commands errors. We can see rule that should be disabled by
`eslintrc.react.cjs` extension made from twenty-front `eslintrc` :
```ts
/Users/paulrastoin/ws/twenty-two/packages/twenty-front/src/modules/settings/data-model/fields/forms/components/__stories__/SettingsDataModelFieldSettingsFormCard.stories.tsx
23:27 warning Forbidden non-null assertion @typescript-eslint/no-non-null-assertion
```
## Fixes
- consume the `twenty-front` package eslint configuration within the
vite lint checker
- eslint overrides extends are getting merged based on glob inclusion of
their files declarations
- any linted files should be included in one of our `tsconfig`
- removed redundant and counter-productive negative `ignorePatterns`, as
eslint will naturally only lint files within configuration file
directory by default which will result making it go through local
`node_modules` project structure
## Now
Less cpu usage <3.5 gb and faster
```ts
// from packages/twenty-front
TIMING=1 npx eslint . --report-unused-disable-directives --max-warnings 0 --config .eslintrc.cjs --debug
#...
Rule | Time (ms) | Relative
:-----------------------------------------------|----------:|--------:
project-structure/folder-structure | 19578.927 | 20.2%
prettier/prettier | 13746.156 | 14.2%
no-redeclare | 9546.570 | 9.9%
@nx/workspace-explicit-boolean-predicates-in-if | 8167.805 | 8.4%
@typescript-eslint/no-unused-vars | 6872.803 | 7.1%
import/no-relative-packages | 6577.273 | 6.8%
@nx/enforce-module-boundaries | 6520.945 | 6.7%
import/no-duplicates | 4987.476 | 5.2%
react/no-direct-mutation-state | 2323.082 | 2.4%
react/require-render-return | 1155.261 | 1.2%
```
## Conclusion
Please note that `nx linter` might not be as strict as vite config
eslint runner
---------
Co-authored-by: Charles Bochet <charlesBochet@users.noreply.github.com>
The @scalar package we use to offer a REST api playground is quite heavy
(3k files compared to the 15k existing) and is not pre-build in the npm
package, which is not unusual.
This is increasing the memory need during vite build.
I'm increasing the RAM available for vite build.
Long term I recommend using a CDN here as this is not really a React
component so we won't benefit from any reactivity anyway. Exaclty like
we have done for FrontApp support chat integration
<img width="1058" alt="image"
src="https://github.com/user-attachments/assets/5412c6c1-7434-4b19-b9ac-e89f1cb614f3"
/>
In this PR, I'm specifying to vite build that
'@scalar/api-reference-react' is an external dependency and should not
be considered as a module we maintain (it won't get its own chunk at
build time and we won't generate sourcemaps on our end).
I'm not sure why vite is considering it internal in the first place (I
can see that it's generating .vue.js files, might be the first time we
are relying on a vue library)
Refers #8128
Changes Introduced:
- Added i18n configuration.
- Added a feature flag for localization.
- Enabled language switching based on the flag.
---------
Co-authored-by: Félix Malfait <felix@twenty.com>