ddedecbb3672d6489afd21a71a808eadd261ff13
502
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
a07337fea0 |
fix: return method-specific MCP responses (#18671)
## Summary Fixes #18524 Fixes the MCP response contract for non-`initialize` methods. Previously, `/mcp` returned initialize-style metadata for methods like `tools/list`, which caused strict MCP clients to reject the response shape. The endpoint also returned `201 Created` for RPC calls even though no resource was being created. ## Changes - return only method-specific payloads for MCP list methods - `tools/list` -> `{ tools: [...] }` - `prompts/list` -> `{ prompts: [] }` - `resources/list` -> `{ resources: [] }` - keep MCP server metadata only on `initialize` - make `/mcp` return `200 OK` instead of `201 Created` - add regression tests for: - `tools/list` response shape - `prompts/list` response shape - `resources/list` response shape ## Why Strict MCP clients expect: - standard RPC transport semantics over HTTP - method-specific JSON-RPC result payloads Returning initialize metadata for non-`initialize` methods breaks that expectation and can cause client deserialization or protocol validation failures. ## Verification - reproduced the issue locally against `/mcp` - verified `tools/list` was previously returning initialize-style fields - verified `tools/list` now returns only `result.tools` - verified `/mcp` now returns `200 OK` - ran targeted Jest tests: ```bash cd /Users/apple/MyProjects/OpenSource/twenty/packages/twenty-server npx jest --runInBand src/engine/api/mcp/services/__tests__/mcp-protocol.service.spec.ts src/engine/api/mcp/services/__tests__/mcp-tool-executor.service.spec.ts --------- Co-authored-by: Félix Malfait <felix@twenty.com> |
||
|
|
5c745059ad |
refactor: remove "core" naming from views and eliminate converter layer (#18667)
## Summary
- **Remove all "core" prefixes** from the views system — the
metadata-based storage migration is complete, so `CoreView`,
`coreViewsSelector`, `getCoreViews`, etc. are now just `View`,
`viewsSelector`, `getViews`
- **Eliminate the entire converter layer** (15 files, ~850 lines
deleted) — `convertCoreViewToView` and all sub-converters were either
no-ops or trivially adding `__typename` / mapping identical enum values.
Local enums now re-export from generated GraphQL types directly (single
source of truth)
- **Unify `View` and `ViewWithRelations`** into one type —
`ViewWithRelations` is now a type alias for `View`, selectors return
data directly without conversion
### Backend
- Rename `@ObjectType('CoreView')` → `@ObjectType('View')` (and all
sub-entities)
- Rename resolver methods: `getCoreViews` → `getViews`, `createCoreView`
→ `createView`, etc.
- Rename `FIND_ALL_CORE_VIEWS_GRAPHQL_OPERATION` →
`FIND_ALL_VIEWS_GRAPHQL_OPERATION`
### Frontend
- Delete 15 converter files (`convertGqlView*ToView*`,
`convertView*ToGql`, `convertViewWithRelationsToView`)
- Re-export `ViewType`, `ViewKey`, `ViewFilterGroupLogicalOperator` from
generated enums (no more duplicate enum definitions with different
casing)
- Replace `ViewOpenRecordInType` with `ViewOpenRecordIn` from generated
- Remove `__typename` from all local view sub-types
- Remove unused `variant` from `ViewFilter`, make `displayValue` and
`definition` optional
- Rename ~45 GraphQL query/mutation files and all selectors to drop
"core" prefix
- Delete unused `viewsWithRelationsSelector`
|
||
|
|
ba9aa41bba |
refactor: metadata store cleanup, SSE unification, mock metadata loading & login redirect fix (#18651)
## Summary - **SSE unification**: Replaced 11 individual SSE effect components with a single generic `MetadataStoreSSEEffect` - **Metadata store cleanup**: Merged `metadataCollectionHashesState` into `metadataStoreState` (currentCollectionHash / draftCollectionHash per entity), moved `objectMetadataItemsSelector` to `object-metadata` domain, converted `navigationMenuItemsState` to a derived selector - **Naming clarity**: Renamed `isAppMetadataReadyState` → `isMinimalMetadataReadyState`, `MetadataGater` → `MinimalMetadataGater`, `useIsLogged` → `useHasAccessTokenPair`, `patchMetadataStoreFromSSEEvent` now takes named object params - **Mock metadata loading**: Added `generate-navigation-menu-items.ts` script, rewrote `useLoadMockedMinimalMetadata` to load full objects/fields/indexes/views/navItems from generated mock data, enabling proper sign-in background rendering (table columns, view picker, navigation) - **Login/logout transitions**: `MinimalMetadataLoadEffect` manages mocked↔real metadata transitions based on auth state, `MainContextStoreProvider` computes context on auth pages for view picker support - **Login redirect fix**: `handleLoadWorkspaceAfterAuthentication` now re-enables `isAppEffectRedirectEnabled` after `loadCurrentUser()` completes, fixing the blocked post-login navigation - **Dead code removal**: Deleted `useRefreshPageLayouts`, `useApplyPageLayouts`, `useStaleMetadataEntities`, `metadataCollectionHashesState`, and all individual SSE effects ## Test plan - [x] Login from welcome page redirects to companies page - [x] Logout transitions cleanly to mocked metadata on welcome page - [x] Sign-in background shows table columns, view picker, and navigation items - [x] SSE events still update metadata store entries correctly - [x] Navigation menu items persist across page refreshes - [ ] CI: lint, typecheck, tests pass |
||
|
|
d9eb317bb5 |
feat: rename RICH_TEXT_V2 → RICH_TEXT in codebase (keep DB value) (#18628)
## Summary - Renames the `FieldMetadataType` enum key from `RICH_TEXT_V2` to `RICH_TEXT` across the entire codebase, while keeping the underlying string value as `'RICH_TEXT_V2'` to maintain PostgreSQL database compatibility - Renames all related types, guards, hooks, components, and files from `*RichTextV2*` / `*rich-text-v2*` to `*RichText*` / `*rich-text*` (e.g. `FormRichTextV2FieldInput` → `FormRichTextFieldInput`, `isFieldRichTextV2` → `isFieldRichText`) - Updates generated files (GraphQL schema, SDK types) to use the new key while preserving the `RICH_TEXT_V2` string value for DB/API layer - Updates i18n locale files, test snapshots, and integration tests to reflect the rename ## Context The legacy `RICH_TEXT` (V1) field type was deprecated and migrated to `TEXT` in a previous PR (#18623). With V1 gone, the `RICH_TEXT_V2` naming is no longer necessary — `RICH_TEXT` is now the canonical name. The DB enum value stays `'RICH_TEXT_V2'` to avoid confusion with the just-deprecated V1 type and to prevent a database migration. ## Test plan - [x] `twenty-server` typecheck passes - [x] `twenty-front` typecheck passes (only pre-existing Apollo client errors remain) - [x] `twenty-server` lint passes - [x] `twenty-front` lint passes - [x] `twenty-shared` build passes - [ ] CI passes Made with [Cursor](https://cursor.com) |
||
|
|
46e515436e |
Deprecate legacy RICH_TEXT field metadata type (#18623)
## Summary - Removes the deprecated `RICH_TEXT` (V1) field metadata type from the codebase entirely - Adds a 1.20 upgrade command that migrates existing `RICH_TEXT` fields to `TEXT` in `core.fieldMetadata` - Cleans up ~70 files across `twenty-shared`, `twenty-server`, `twenty-front`, `twenty-sdk`, and `twenty-zapier` ## Context `RICH_TEXT` was a legacy field type that stored rich text as a single `text` column. It was already **read-only** — writes threw errors directing users to `RICH_TEXT_V2` instead. `RICH_TEXT_V2` is the current approach: a composite type with `blocknote` (editor JSON) and `markdown` subfields. Keeping the deprecated type added maintenance burden without any value. Since the underlying database column type for `RICH_TEXT` was already `text` (same as `TEXT`), the migration only needs to update the metadata — no data migration or column changes required. ## Changes ### Upgrade command (new) - `1-20-migrate-rich-text-to-text.command.ts` — runs `UPDATE core."fieldMetadata" SET "type" = 'TEXT' WHERE "type" = 'RICH_TEXT'` per workspace, with cache invalidation ### Enum & shared types - Removed `RICH_TEXT` from `FieldMetadataType` enum - Removed from `FieldMetadataDefaultValueMapping`, `isFieldMetadataTextKind` ### Server (~30 files) - Removed from type mapper (scalar, filter, order-by), data processors, input transformer, filter operators, zod schemas, column type mapping, searchable fields, RLS matching, OpenAPI schema, fake value generators - Removed from field creation flow and field metadata type validator - Updated dev seeder Pet `bio` field to `TEXT` - Cleaned up mocks, snapshots, integration tests ### Frontend (~25 files) - Deleted: `RichTextFieldDisplay`, `isFieldRichText`, `isFieldRichTextValue`, `useRichTextFieldDisplay` - Removed from `FieldDisplay`, `usePersistField`, `isFieldValueEmpty`, `isRecordMatchingFilter`, `generateEmptyFieldValue`, `isFieldCellSupported`, spreadsheet import, workflow fake values - Removed from settings types, field type configs, and field creation exclusion list - Updated tests, mocks, and stories ### SDK & Zapier - Removed from generated GraphQL schema and TypeScript types - Removed from Zapier `computeInputFields` |
||
|
|
e8f8189167 |
[COMMAND MENU ITEMS] Add engine component key (#18554)
## PR Description In the process of migrating all the existing commands to the backend, we stumbled across a couple of problems that made us reconsider the full migration. This PR introduces a way for command menu items to bypass front components and to directly reference a frontend component from twenty front. It: - Introduces a `engineFrontComponentKey` field on `CommandMenuItem` as an alternative to `frontComponentId` and `workflowVersionId`, allowing command menu items to reference frontend components by key directly rather than requiring a FrontComponent entity - Updates the DB constraint to allow exactly one of `workflowVersionId`, `frontComponentId`, or `engineFrontComponentKey` ### All standard command menu items from the frontend which use `standardFrontComponentKey` These are all commands that execute a GraphQL query or a mutation. Two mains concerned have been raised that made us go with this (temporary) architecture instead: - If those commands are part of the standard application, they can only alter objects from that application and not custom objects. - We would need to implement a way to trigger optimistic rendering from the front components, which might take some time to implement. List: - Create new record - Delete (single record) - Delete records (multiple) - Restore record - Restore records (multiple) - Permanently destroy record - Permanently destroy records (multiple) - Add to favorites - Remove from favorites - Merge records - Duplicate Dashboard - Save Dashboard - Save Page Layout - Activate Workflow - Deactivate Workflow - Discard Draft (workflow) - Test Workflow - Tidy up workflow - Duplicate Workflow - Stop (workflow run) - Use as draft (workflow version) --------- Co-authored-by: Charles Bochet <charles@twenty.com> |
||
|
|
6995420b71 |
Remove IS_APPLICATION_INSTALLATION_FROM_TARBALL_ENABLED feature flag (#18520)
## Summary - Removes the `IS_APPLICATION_INSTALLATION_FROM_TARBALL_ENABLED` feature flag, consolidating tarball-based app installation under the existing `IS_APPLICATION_ENABLED` flag - Removes the runtime feature flag check in `runWorkspaceMigration` resolver (the `@RequireFeatureFlag(IS_APPLICATION_ENABLED)` decorator already gates this endpoint) - Cleans up related integration test setup/teardown and mock feature flag maps ## Test plan - [ ] Verify tarball-based app installation still works when `IS_APPLICATION_ENABLED` is true - [ ] Verify app installation is blocked when `IS_APPLICATION_ENABLED` is false - [ ] Run `failing-install-application.integration-spec.ts` to confirm it passes without the removed flag Made with [Cursor](https://cursor.com) |
||
|
|
c433b2b73f | Implement page layout override (#18472) | ||
|
|
06bdb5ad6a |
[SDK] Agent in manifest (#18431)
# Introduction Adding agent in the manifest, required for twenty standard app extraction out of twenty-server |
||
|
|
66d93c4d28 |
Fix app:dev CLI by removing deleted createOneApplication mutation (#18460)
## Summary - The `createOneApplication` GraphQL mutation was removed from the server during the application architecture refactor (#18432), but the SDK CLI (`app:dev`, `app:build --sync`) still called it, causing failures. - Simplified the SDK to use `syncApplication` (which now internally creates the `ApplicationEntity` via `ensureApplicationExists`) instead of a separate create step. - On first run (clean install), the orchestrator now runs an initial sync before initializing the file uploader, so file uploads can proceed (they require the `ApplicationEntity` to exist). ## Test plan - [x] Typecheck passes for both `twenty-sdk` and `twenty-server` - [x] `app:dev` tested locally with existing app (finds app, uploads, syncs) - [x] `app:dev` tested locally after `app:uninstall` (creates app via sync, uploads, syncs) - [x] SDK unit tests pass (23/26 files, 3 pre-existing failures unrelated) Made with [Cursor](https://cursor.com) |
||
|
|
403db7ad3f |
Add default viewField when creating object (#18441)
as title |
||
|
|
514d0017ea |
Refactor application module architecture for clarity and explicitness (#18432)
## Summary - **Module reorganization**: Moved `ApplicationUpgradeService` and cron jobs to `application-upgrade/`, `ApplicationSyncService` to `application-manifest/`, and `runWorkspaceMigration`/`uninstallApplication` mutations to the manifest resolver — each module now has a single clear responsibility. - **Explicit install flow**: Removed implicit `ApplicationEntity` creation from `ApplicationSyncService`. The install service and dev resolver now explicitly create the `ApplicationEntity` before syncing. npm packages are resolved at registration time to extract manifest metadata (universalIdentifier, name, description, etc.), eliminating the `reconcileUniversalIdentifier` hack. - **Better error handling**: Frontend hooks now surface actual server error messages in snackbars instead of swallowing them. Replaced the ugly `ConfirmationModal` for transfer ownership with a proper form modal. Fixed `SettingsAdminTableCard` row height overflow and corrected the `yarn-engine` asset path. ## Test plan - [ ] Register an npm package — verify manifest metadata (name, description, universalIdentifier) is extracted correctly - [ ] Install a registered npm app on a workspace — verify ApplicationEntity is created and sync succeeds - [ ] Test `app:dev` CLI flow — verify local app registration and sync work - [ ] Upload a tarball — verify registration and install flow - [ ] Transfer ownership — verify the new modal UX works - [ ] Verify error messages appear correctly in snackbars when operations fail Made with [Cursor](https://cursor.com) |
||
|
|
9d57bc39e5 |
Migrate from ESLint to OxLint (#18443)
## Summary Fully replaces ESLint with OxLint across the entire monorepo: - **Replaced all ESLint configs** (`eslint.config.mjs`) with OxLint configs (`.oxlintrc.json`) for every package: `twenty-front`, `twenty-server`, `twenty-emails`, `twenty-ui`, `twenty-shared`, `twenty-sdk`, `twenty-zapier`, `twenty-docs`, `twenty-website`, `twenty-apps/*`, `create-twenty-app` - **Migrated custom lint rules** from ESLint plugin format to OxLint JS plugin system (`@oxlint/plugins`), including `styled-components-prefixed-with-styled`, `no-hardcoded-colors`, `sort-css-properties-alphabetically`, `graphql-resolvers-should-be-guarded`, `rest-api-methods-should-be-guarded`, `max-consts-per-file`, and Jotai-related rules - **Migrated custom rule tests** from ESLint `RuleTester` + Jest to `oxlint/plugins-dev` `RuleTester` + Vitest - **Removed all ESLint dependencies** from `package.json` files and regenerated lockfiles - **Updated Nx targets** (`lint`, `lint:diff-with-main`, `fmt`) in `nx.json` and per-project `project.json` to use `oxlint` commands with proper `dependsOn` for plugin builds - **Updated CI workflows** (`.github/workflows/ci-*.yaml`) — no more ESLint executor - **Updated IDE setup**: replaced `dbaeumer.vscode-eslint` with `oxc.oxc-vscode` extension, configured `source.fixAll.oxc` and format-on-save with Prettier - **Replaced all `eslint-disable` comments** with `oxlint-disable` equivalents across the codebase - **Updated docs** (`twenty-docs`) to reference OxLint instead of ESLint - **Renamed** `twenty-eslint-rules` package to `twenty-oxlint-rules` ### Temporarily disabled rules (tracked in `OXLINT_MIGRATION_TODO.md`) | Rule | Package | Violations | Auto-fixable | |------|---------|-----------|-------------| | `twenty/sort-css-properties-alphabetically` | twenty-front | 578 | Yes | | `typescript/consistent-type-imports` | twenty-server | 3814 | Yes | | `twenty/max-consts-per-file` | twenty-server | 94 | No | ### Dropped plugins (no OxLint equivalent) `eslint-plugin-project-structure`, `lingui/*`, `@stylistic/*`, `import/order`, `prefer-arrow/prefer-arrow-functions`, `eslint-plugin-mdx`, `@next/eslint-plugin-next`, `eslint-plugin-storybook`, `eslint-plugin-react-refresh`. Partial coverage for `jsx-a11y` and `unused-imports`. ### Additional fixes (pre-existing issues exposed by merge) - Fixed `EmailThreadPreview.tsx` broken import from main rename (`useOpenEmailThreadInSidePanel`) - Restored truthiness guard in `getActivityTargetObjectRecords.ts` - Fixed `AgentTurnResolver` return types to match entity (virtual `fileMediaType`/`fileUrl` are resolved via `@ResolveField()`) ## Test plan - [x] `npx nx lint twenty-front` passes - [x] `npx nx lint twenty-server` passes - [x] `npx nx lint twenty-docs` passes - [x] Custom oxlint rules validated with Vitest: `npx nx test twenty-oxlint-rules` - [x] `npx nx typecheck twenty-front` passes - [x] `npx nx typecheck twenty-server` passes - [x] CI workflows trigger correctly with `dependsOn: ["twenty-oxlint-rules:build"]` - [x] IDE linting works with `oxc.oxc-vscode` extension |
||
|
|
abd9709291 |
Update Command Menu Item entity (#18391)
Closes https://github.com/twentyhq/core-team-issues/issues/2256 |
||
|
|
0e89c96170 |
feat: add npm and tarball app distribution with upgrade mechanism (#18358)
## Summary - **npm + tarball app distribution**: Apps can be installed from the npm registry (public or private) or uploaded as `.tar.gz` tarballs, with `AppRegistrationSourceType` tracking the origin - **Upgrade mechanism**: `AppUpgradeService` checks for newer versions, supports rollback for npm-sourced apps, and a cron job runs every 6 hours to update `latestAvailableVersion` on registrations - **Security hardening**: Tarball extraction uses path traversal protection, and `enableScripts: false` in `.yarnrc.yml` disables all lifecycle scripts during `yarn install` to prevent RCE - **Frontend**: "Install from npm" and "Upload tarball" modals, upgrade button on app detail page, blue "Update" badge on installed apps table when a newer version is available - **Marketplace catalog sync**: Hourly cron job syncs a hardcoded catalog index into `ApplicationRegistration` entities - **Integration tests**: Coverage for install, upgrade, tarball upload, and catalog sync flows ## Backend changes | Area | Files | |------|-------| | Entity & migration | `ApplicationRegistrationEntity` (sourceType, sourcePackage, latestAvailableVersion), `ApplicationEntity` (applicationRegistrationId), migration | | Services | `AppPackageResolverService`, `ApplicationInstallService`, `AppUpgradeService`, `MarketplaceCatalogSyncService` | | Cron jobs | `MarketplaceCatalogSyncCronJob` (hourly), `AppVersionCheckCronJob` (every 6h) | | REST endpoint | `AppRegistrationUploadController` — tarball upload with secure extraction | | Resolver | `MarketplaceResolver` — simplified `installMarketplaceApp` (removed redundant `sourcePackage` arg) | | Security | `.yarnrc.yml` — `enableScripts: false` to block postinstall RCE | ## Frontend changes | Area | Files | |------|-------| | Modals | `SettingsInstallNpmAppModal`, `SettingsUploadTarballModal`, `SettingsAppModalLayout` | | Hooks | `useUploadAppTarball`, `useInstallMarketplaceApp` (cleaned up) | | Upgrade UI | `SettingsApplicationVersionContainer`, `SettingsApplicationDetailAboutTab` | | Badge | `SettingsApplicationTableRow` — blue "Update" tag, `SettingsApplicationsInstalledTab` — fetches registrations for version comparison | | Styling | Migrated to Linaria (matching main) | ## Test plan - [ ] Install an app from npm via the "Install from npm" modal - [ ] Upload a `.tar.gz` tarball via the "Upload tarball" modal - [ ] Verify upgrade badge appears when `latestAvailableVersion > version` - [ ] Verify upgrade flow from app detail page - [ ] Run integration tests: `app-distribution.integration-spec.ts`, `marketplace-catalog-sync.integration-spec.ts` - [ ] Verify `enableScripts: false` blocks postinstall scripts during yarn install Made with [Cursor](https://cursor.com) |
||
|
|
bfa50f566e |
Bump @clickhouse/client from 1.11.0 to 1.18.1 (#18410)
Bumps [@clickhouse/client](https://github.com/ClickHouse/clickhouse-js) from 1.11.0 to 1.18.1. <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/ClickHouse/clickhouse-js/releases"><code>@clickhouse/client</code>'s releases</a>.</em></p> <blockquote> <h2>1.18.1</h2> <h2>Improvements</h2> <ul> <li>Setting <code>log.level</code> default value to <code>ClickHouseLogLevel.WARN</code> instead of <code>ClickHouseLogLevel.OFF</code> to provide better visibility into potential issues without overwhelming users with too much information by default.</li> </ul> <pre lang="ts"><code>const client = createClient({ // ... log: { level: ClickHouseLogLevel.WARN, // default is now ClickHouseLogLevel.WARN instead of ClickHouseLogLevel.OFF }, }) </code></pre> <ul> <li>Logging is now lazy, which means that the log messages will only be constructed if the log level is appropriate for the message. This can improve performance in cases where constructing the log message is expensive, and the log level is set to ignore such messages. See <code>ClickHouseLogLevel</code> enum for the complete list of log levels. (<a href="https://redirect.github.com/ClickHouse/clickhouse-js/issues/520">#520</a>)</li> </ul> <pre lang="ts"><code>const client = createClient({ // ... log: { level: ClickHouseLogLevel.TRACE, // to log everything available down to the network level events }, }) </code></pre> <ul> <li>Enhanced the logging of the HTTP request / socket lifecycle with additional trace messages and context such as Connection ID (UUID) and Request ID and Socket ID that embed the connection ID for ease of tracing the logs of a particular request across the connection lifecycle. To enable such logs, set the <code>log.level</code> config option to <code>ClickHouseLogLevel.TRACE</code>. (<a href="https://redirect.github.com/ClickHouse/clickhouse-js/issues/567">#567</a>)</li> </ul> <pre lang="console"><code>[2026-02-25T09:19:13.511Z][TRACE][@clickhouse/client][Connection] Insert: received 'close' event, 'free' listener removed Arguments: { operation: 'Insert', connection_id: 'da3c9796-5dc5-46ef-83b0-ed1f4422094c', query_id: '9dfda627-39a2-41a6-9fc9-8f8716574826', request_id: 'da3c9796-5dc5-46ef-83b0-ed1f4422094c:3', socket_id: 'da3c9796-5dc5-46ef-83b0-ed1f4422094c:2', event: 'close' } [2026-02-25T09:19:13.502Z][TRACE][@clickhouse/client][Connection] Query: reusing socket Arguments: { operation: 'Query', connection_id: 'da3c9796-5dc5-46ef-83b0-ed1f4422094c', query_id: 'ad0127e8-b1c7-4ed6-9681-c0162f7a0ea9', request_id: 'da3c9796-5dc5-46ef-83b0-ed1f4422094c:4', socket_id: 'da3c9796-5dc5-46ef-83b0-ed1f4422094c:2', usage_count: 1 } </code></pre> <ul> <li>A step towards structured logging: the client now passes rich context to the logger <code>args</code> parameter (e.g. <code>connection_id</code>, <code>query_id</code>, <code>request_id</code>, <code>socket_id</code>). (<a href="https://redirect.github.com/ClickHouse/clickhouse-js/issues/576">#576</a>)</li> </ul> <!-- raw HTML omitted --> </blockquote> <p>... (truncated)</p> </details> <details> <summary>Changelog</summary> <p><em>Sourced from <a href="https://github.com/ClickHouse/clickhouse-js/blob/main/CHANGELOG.md"><code>@clickhouse/client</code>'s changelog</a>.</em></p> <blockquote> <h1>1.18.1</h1> <h2>Improvements</h2> <ul> <li>Setting <code>log.level</code> default value to <code>ClickHouseLogLevel.WARN</code> instead of <code>ClickHouseLogLevel.OFF</code> to provide better visibility into potential issues without overwhelming users with too much information by default.</li> </ul> <pre lang="ts"><code>const client = createClient({ // ... log: { level: ClickHouseLogLevel.WARN, // default is now ClickHouseLogLevel.WARN instead of ClickHouseLogLevel.OFF }, }) </code></pre> <ul> <li>Logging is now lazy, which means that the log messages will only be constructed if the log level is appropriate for the message. This can improve performance in cases where constructing the log message is expensive, and the log level is set to ignore such messages. See <code>ClickHouseLogLevel</code> enum for the complete list of log levels. (<a href="https://redirect.github.com/ClickHouse/clickhouse-js/issues/520">#520</a>)</li> </ul> <pre lang="ts"><code>const client = createClient({ // ... log: { level: ClickHouseLogLevel.TRACE, // to log everything available down to the network level events }, }) </code></pre> <ul> <li>Enhanced the logging of the HTTP request / socket lifecycle with additional trace messages and context such as Connection ID (UUID) and Request ID and Socket ID that embed the connection ID for ease of tracing the logs of a particular request across the connection lifecycle. To enable such logs, set the <code>log.level</code> config option to <code>ClickHouseLogLevel.TRACE</code>. (<a href="https://redirect.github.com/ClickHouse/clickhouse-js/issues/567">#567</a>)</li> </ul> <pre lang="console"><code>[2026-02-25T09:19:13.511Z][TRACE][@clickhouse/client][Connection] Insert: received 'close' event, 'free' listener removed Arguments: { operation: 'Insert', connection_id: 'da3c9796-5dc5-46ef-83b0-ed1f4422094c', query_id: '9dfda627-39a2-41a6-9fc9-8f8716574826', request_id: 'da3c9796-5dc5-46ef-83b0-ed1f4422094c:3', socket_id: 'da3c9796-5dc5-46ef-83b0-ed1f4422094c:2', event: 'close' } [2026-02-25T09:19:13.502Z][TRACE][@clickhouse/client][Connection] Query: reusing socket Arguments: { operation: 'Query', connection_id: 'da3c9796-5dc5-46ef-83b0-ed1f4422094c', query_id: 'ad0127e8-b1c7-4ed6-9681-c0162f7a0ea9', request_id: 'da3c9796-5dc5-46ef-83b0-ed1f4422094c:4', socket_id: 'da3c9796-5dc5-46ef-83b0-ed1f4422094c:2', usage_count: 1 } </code></pre> <ul> <li>A step towards structured logging: the client now passes rich context to the logger <code>args</code> parameter (e.g. <code>connection_id</code>, <code>query_id</code>, <code>request_id</code>, <code>socket_id</code>). (<a href="https://redirect.github.com/ClickHouse/clickhouse-js/issues/576">#576</a>)</li> </ul> <!-- raw HTML omitted --> </blockquote> <p>... (truncated)</p> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/ClickHouse/clickhouse-js/commit/cbdd7bf20904626956e0ff7808d17015813400c1"><code>cbdd7bf</code></a> Release 1.18.1 (<a href="https://redirect.github.com/ClickHouse/clickhouse-js/issues/590">#590</a>)</li> <li><a href="https://github.com/ClickHouse/clickhouse-js/commit/c9f61ebb3a2ec6201f87417e30c4fc4271451ae8"><code>c9f61eb</code></a> Beta 1.18.0 (<a href="https://redirect.github.com/ClickHouse/clickhouse-js/issues/588">#588</a>)</li> <li><a href="https://github.com/ClickHouse/clickhouse-js/commit/d0f67b71ef896d47fc3d8d0942612ced22aa79dc"><code>d0f67b7</code></a> Split public and internal <code>drainStream</code> and cover with tests (<a href="https://redirect.github.com/ClickHouse/clickhouse-js/issues/578">#578</a>)</li> <li><a href="https://github.com/ClickHouse/clickhouse-js/commit/535e9b726e328ce8468c159f935c27910731f4bb"><code>535e9b7</code></a> Remove <code>unsafeLogUnredactedQueries</code> for now (<a href="https://redirect.github.com/ClickHouse/clickhouse-js/issues/580">#580</a>)</li> <li><a href="https://github.com/ClickHouse/clickhouse-js/commit/44e73c73019a3956c1fac67ce0f4f170b3a4f19a"><code>44e73c7</code></a> Default log level to <code>WARN</code> (<a href="https://redirect.github.com/ClickHouse/clickhouse-js/issues/581">#581</a>)</li> <li><a href="https://github.com/ClickHouse/clickhouse-js/commit/5146fbc13e5c23d08e2cc5773bea0adf58d83a5c"><code>5146fbc</code></a> Focus AI on security and API stability (<a href="https://redirect.github.com/ClickHouse/clickhouse-js/issues/579">#579</a>)</li> <li><a href="https://github.com/ClickHouse/clickhouse-js/commit/b7b1d8d7ffe9b6786c9e883379d3edc5a5ed5c58"><code>b7b1d8d</code></a> Trivial E2E test against <code>beta</code> (<a href="https://redirect.github.com/ClickHouse/clickhouse-js/issues/577">#577</a>)</li> <li><a href="https://github.com/ClickHouse/clickhouse-js/commit/761e29ebb5d1bd7a107d2535b385b6565b7120f5"><code>761e29e</code></a> Structured logs, part 1 (<a href="https://redirect.github.com/ClickHouse/clickhouse-js/issues/576">#576</a>)</li> <li><a href="https://github.com/ClickHouse/clickhouse-js/commit/fd23dd7fc9e91ff810a7bb45984a24682ba25482"><code>fd23dd7</code></a> Provide more context in logs for connection and request handling (<a href="https://redirect.github.com/ClickHouse/clickhouse-js/issues/567">#567</a>)</li> <li><a href="https://github.com/ClickHouse/clickhouse-js/commit/a7866e72e356244cae9d20d9fef38a6aafe68ba8"><code>a7866e7</code></a> Adjusting CI DevX (<a href="https://redirect.github.com/ClickHouse/clickhouse-js/issues/574">#574</a>)</li> <li>Additional commits viewable in <a href="https://github.com/ClickHouse/clickhouse-js/compare/1.11.0...1.18.1">compare view</a></li> </ul> </details> <details> <summary>Maintainer changes</summary> <p>This version was pushed to npm by <a href="https://www.npmjs.com/~4b819b88c84b">4b819b88c84b</a>, a new releaser for <code>@clickhouse/client</code> since your current version.</p> </details> <br /> [](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- <details> <summary>Dependabot commands and options</summary> <br /> You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself) - `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself) </details> --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Abdullah <125115953+mabdullahabaid@users.noreply.github.com> |
||
|
|
b11f77df2a |
[FRONT COMPONENTS] Introduce conditionalAvailabilityExpression to command menu items (#18319)
## PR Description - Uses `expr-eval` to enable front components (SDK plugins) to define conditional availability as declarative expressions. - Moves shared types and constants to `twenty-shared` - Introduces a `conditionalAvailabilityExpression` field on `CommandMenuItemEntity`, allowing command menu items to store an `expr-eval` compatible expression string that is evaluated against a CommandMenuContext to determine if the item should be shown. - Creates an esbuild transform plugin `conditional-availability-transform-plugin` in `twenty-sdk` that converts TypeScript conditional availability expressions into `expr-eval` compatible syntax at build time, so SDK developers can write natural TS expressions that get transformed to evaluable strings. - Removes deprecated `forceRegisteredActionsByKey` state and its usage. - Creates `useCommandMenuContext` hook that builds the full `CommandMenuContext` object from React state, which is then passed to `useCommandMenuItemFrontComponentActions` for evaluating conditional availability expressions. |
||
|
|
c97d872b9f |
[BREAKING_CHANGE_VIEW_SORT] Refactor view sort to v2 (#17609)
Fixes https://github.com/twentyhq/core-team-issues/issues/2187 --------- Co-authored-by: prastoin <paul@twenty.com> Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com> |
||
|
|
906a0aed38 |
Common API - Filter validation layer (#18187)
Closes https://github.com/twentyhq/core-team-issues/issues/1627 **FilterArgProcessor consolidation:** Refactored to both validate AND transform filter values in a single pass Coerced string inputs to native types (e.g., "1" → 1, "true" → true - useful for Rest input) Returns transformed filter instead of just validating Removed overrideFilterByFieldMetadata calls from all computeArgs methods **QueryRunnerArgsFactory cleanup** **Testing:** Add unit testing uncomment integration tests |
||
|
|
80d054563e |
followup: centralize widget common properties and add widget bulk update integration tests (#18225)
followup https://github.com/twentyhq/twenty/pull/18015#pullrequestreview-3818929035 |
||
|
|
2e9624858c |
Fix name singular updates in dev mode (#18339)
as title |
||
|
|
58e37a118c |
Builder runs delete update and then create (#18272)
# Introduction We need to build and validate the flat entity operation in the following order delete update and create For example if not, if a created field has the same name than a deleted one than it will fail whereas it should not |
||
|
|
20a2c3836e |
feat: introduce role selector when inviting members to a workspace (#18085)
This PR adds an explicit role selector to the "Invite by email" flow,
requires a role choice before sending, and stores the selected role with
each invitation. The backend now accepts and persists `roleId` on
invitations and applies it when the invite is accepted, while keeping it
optional to avoid breaking existing clients and legacy invites.
---
### Frontend
- **Settings → Members → Invite by email**
- New **Role** dropdown (same `Select` pattern as member/API key role
selectors) between the email input and Invite button.
- Roles are loaded via `SettingsRolesQueryEffect` and
`settingsAllRolesSelector`; only roles with `canBeAssignedToUsers` are
shown.
- Role is **required**: form validates `roleId` (e.g.
`z.string().min(1)`) and the Invite button is disabled until a role is
selected and emails are valid.
- `WorkspaceInviteTeam` receives `roles` as a prop from the parent;
layout is responsive (e.g. stacked on small viewports).
- **Pending invitations table**
- New **Role** column showing the invitation’s role label (or "Unknown
role" for legacy invites without `roleId`), using the same roles source
for lookup.
- **Onboarding invite step**
- When sending invites during onboarding, the workspace **default role**
is used when available (`currentWorkspace?.defaultRole?.id`), so no role
selector is added there.
- **GraphQL**
- `sendInvitations` mutation accepts optional `roleId`;
`findWorkspaceInvitations` and resend mutation responses include
`roleId` on `WorkspaceInvitation`. Frontend types (e.g.
`WorkspaceInvitation`, hook variables) updated accordingly.
---
### Backend
- **API**
- `SendInvitationsInput` has an **optional** `roleId` (UUID, nullable).
The resolver normalises `null` to `undefined` so existing callers and
legacy flows are not broken.
- **Validation (when `roleId` is provided)**
- Role checks are centralised in **RoleValidationService**
(`RoleValidationModule`, in `metadata-modules/role-validation/`). It
validates that the role exists in the workspace and has
`canBeAssignedToUsers`, and throws a permissions-style error otherwise.
This avoids circular dependencies (e.g. `RoleModule` imports
`UserWorkspaceModule`, so invite/accept flows cannot depend on
`RoleModule`).
- **Send flow:** `WorkspaceInvitationResolver` and
`WorkspaceInvitationService.sendInvitations` both call
`RoleValidationService.validateRoleAssignableToUsersOrThrow` when
`roleId` is present (resolver before calling the service; service again
before creating tokens so that **resend** also validates the stored role
and fails fast if the role was deleted or made unassignable).
- **Accept flow:**
`UserWorkspaceService.addUserToWorkspaceIfUserNotInWorkspace` uses the
same service in `resolveRoleIdForNewMember` when an invitation provides
a `roleId`, then falls back to `workspace.defaultRoleId` when not.
Role/default is resolved and validated before any user/workspace/member
creation.
- **Persistence**
- Invitation app tokens store `roleId` in `context` next to `email`
(`context: { email, roleId? }`). `generateInvitationToken` and
`createWorkspaceInvitation` accept an optional `roleId` and only add it
to `context` when defined.
- **Resend**
- Resend passes the existing invitation’s `context.roleId` into
`sendInvitations`. The service validates that role (when present) before
creating the new token, so if the role was deleted or made unassignable,
resend fails with a clear error instead of sending a broken link.
- **Response shape**
- `SendInvitationsOutput.result` remains `WorkspaceInvitation[]`. When
`usePersonalInvitation` is false we only push full invitation records
(from `castAppTokenToWorkspaceInvitationUtil`), so the result always
matches the GraphQL type (`id`, `email`, `roleId`, `expiresAt`).
- **Modules**
- `WorkspaceInvitationModule` and `UserWorkspaceModule` import
**RoleValidationModule** (not `RoleModule`) and inject
**RoleValidationService** for validation. `RoleModule` imports
`RoleValidationModule` and `RoleService` delegates to
`RoleValidationService` for the same validation where the module graph
allows.
---
### Backward compatibility
- **Optional `roleId`**: Clients that don’t send `roleId` (or send
`null`) are unchanged; invitations are created without a role and the
accept flow uses the workspace default role.
- **Legacy invitations**: App tokens with only `context.email` still
work; `context.roleId` is optional and the UI can show e.g. "Unknown
role" for those in the pending-invitations table.
|
||
|
|
1a8be234de |
OAuth security hardening: RFC compliance, PKCE binding, rate limiting (#18305)
## Summary Follow-up to #18267. Hardens the OAuth implementation with security fixes identified during audit: **P0 — Critical:** - Bind authorization codes to `client_id` in context to prevent auth code injection (RFC 6749 §4.1.3) - Store PKCE `code_challenge` directly in auth code context instead of a separate `CodeChallenge` token — cryptographically binds the challenge to its code - Enforce `code_verifier` when `code_challenge` was used during authorization - Hash authorization codes (SHA-256) before storage to prevent exposure if DB is compromised - Add `Cache-Control: no-store` + `Pragma: no-cache` headers on token responses (RFC 6749 §5.1) - Add rate limiting on `/oauth/token` endpoint (20 req/min per client via existing `ThrottlerService`) **P1 — High:** - Return HTTP 401 for `invalid_client` errors instead of 400 (RFC 6749 §5.2) - Verify refresh tokens belong to the presenting client (cross-client token theft prevention) - Limit fields exposed by public `findApplicationRegistrationByClientId` query to only what the frontend needs (`id`, `name`, `logoUrl`, `websiteUrl`, `oAuthScopes`) - Require `API_KEYS_AND_WEBHOOKS` permission for `createApplicationRegistration` mutation **P2/P3 — Medium/Low:** - Add error handling and loading states to frontend Authorize page - Rename redirect URL param from `authorizationCode` to `code` (RFC standard) - Add unit tests for `validateRedirectUri` utility (8 test cases) ## Test plan - [ ] Existing OAuth integration tests updated for all changes (hashed codes, context-based PKCE, client binding, 401 status codes, cache headers) - [ ] New test: auth code rejected when presented by a different client - [ ] New test: refresh token rejected when presented by a different client - [ ] New test: `code_verifier` required when PKCE was used in authorization - [ ] New test: `Cache-Control: no-store` header present on responses - [ ] New unit tests for `validateRedirectUri` (HTTPS, localhost, fragments, invalid URIs) - [ ] Verify frontend authorize page shows errors gracefully Made with [Cursor](https://cursor.com) |
||
|
|
012d819557 |
OAuth Client — Unified ApplicationRegistration, OAuth server, and frontend (#18267)
## Summary Consolidates three separate PRs (#18260, #18261, #18262) into a single unified branch with all review feedback addressed: ### New features - **ApplicationRegistration entity** — server-level registration for OAuth apps with encrypted server variables - **OAuth 2.0 server** — authorization code, client credentials, refresh token grants with PKCE support - **OAuth discovery endpoint** — `.well-known/oauth-authorization-server` metadata - **Frontend UI** — app registration details page with credential management, redirect URI editing, and server variable configuration - **CLI integration** — `twenty dev` auto-registers apps and stores OAuth credentials locally - **Authorize consent screen** — OAuth consent page at `/authorize` showing requested scopes ### Review feedback addressed **Renames (PR #18260):** - `appRegistration` → `applicationRegistration` (entity, tables, files, imports, GraphQL types) - `appRegistrationVariable` → `applicationRegistrationVariable` - `clientId` → `oAuthClientId`, `clientSecretHash` → `oAuthClientSecretHash`, `redirectUris` → `oAuthRedirectUris`, `scopes` → `oAuthScopes` **Security fixes (PR #18261):** - Fixed redirect URI validation bypass when `oAuthRedirectUris` is an empty array - Fixed workspace isolation in `clientCredentialsGrant` — now uses `find()` with explicit handling for multiple installations - Added error logging in refresh token `catch` block instead of silently swallowing **Code quality (PR #18262):** - Split `VersionDistributionEntry` into its own file (one export per file) - Split GraphQL queries and mutations into individual files with a shared fragment - Removed unused `OAuth` entry from `AuthProviderEnum` - Added loading state to `handleRotateSecret` - Removed 27 narration-style comments from test files - Added proper guards (`PublicEndpointGuard`, `NoPermissionGuard`) to controllers and resolvers ## Test plan - [ ] Verify `twenty dev` registers an app and stores OAuth credentials - [ ] Test OAuth authorization code flow end-to-end (authorize → token → API call) - [ ] Test client credentials grant - [ ] Verify redirect URI validation rejects requests when no URIs are registered - [ ] Verify app registration detail page renders correctly - [ ] Test secret rotation with loading state - [ ] Verify server variable editing and saving - [ ] Run `npx nx database:reset twenty-server` to validate migration Closes #18260, #18261, #18262 Made with [Cursor](https://cursor.com) --------- Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com> |
||
|
|
cfad24da48 |
Fix empty user id clickhouse (#18238)
- Fixes: - Make Workspace User select work; previously, it didn't work as we were not fetching the workspace users correctly - Send Object Events with valid record id and object id ## Audit logs demo https://github.com/user-attachments/assets/92437037-d253-4810-a138-7c709550755d |
||
|
|
5e19361494 |
Allow uuidv5 for universal identifier (#18265)
Twenty apps are using v5 |
||
|
|
120096346a |
Add define post isntall logic function (#18248)
As title |
||
|
|
fde8168a85 | Centralized universal identifier validation on create (#18258) | ||
|
|
98e73791a4 |
Resolve involved application ids from API metadata (#18221)
# Introduction Resolving each create|updated|deleted entities related entities application through their universal foreingKey ( silently failing if not found leaving the validator handling that ) In order to compute all the required application in the dependency flat entity maps ## Tested Creating a field on a custom local twenty-apps installed on the workspace + view field ## Out of scope - updated snapshot - update graphql generated front |
||
|
|
f080b952eb |
Add manifest integration tests (#18203)
- add integration test on sync manifest endpoint - fix invalid standard uuid + migration command |
||
|
|
c6ec764b23 | Fix ci-server ci (#18180) | ||
|
|
af0af0a237 |
Fix cross app installation (#18151)
# Introduction - Fixing app dependencyFlatEntityMaps load ( to load current app + dependent app ) - Fix empty flat entity maps and undefined optimistic override - Refactor the optimistic rendering to be mutation oriented at orchestrator level |
||
|
|
9a0295fd3d |
feat: add atomic upsertFieldsWidget mutation to replace multiple view field group/field API calls (#18137)
The `useSaveFieldsWidgetGroups` hook was making multiple sequential API
calls per widget (create groups, delete groups, update groups, update
fields) — non-atomic, chatty, and with heavy diff computation on the
frontend.
## Backend
- **New input DTOs**: `UpsertFieldsWidgetInput` /
`UpsertFieldsWidgetGroupInput` / `UpsertFieldsWidgetFieldInput` — caller
passes the full desired state of groups + fields for a widget
- **`FieldsWidgetUpsertService`**: looks up widget → resolves `viewId`,
diffs against existing flat entity maps, builds optimistic group maps so
newly-created groups can be referenced by field updates in the same
pass, then runs creates/updates/deletes in a single
`validateBuildAndRunWorkspaceMigration` call
- **`upsertFieldsWidget` mutation** added to `ViewFieldGroupResolver`;
new `FIELDS_WIDGET_NOT_FOUND` exception code added and wired into the
GraphQL exception filter
## Frontend
- New `UPSERT_FIELDS_WIDGET` GQL document
- `useSaveFieldsWidgetGroups` replaces all per-operation calls with a
single mutation per widget, passing the full draft state — diff logic
moves entirely to the backend
```graphql
mutation UpsertFieldsWidget($input: UpsertFieldsWidgetInput!) {
upsertFieldsWidget(input: $input) {
id
name
position
isVisible
viewId
viewFields { id isVisible position ... }
}
}
```
Widget IDs are collected from both draft **and** persisted state so
widgets whose draft groups were cleared still trigger deletion of their
server-side groups.
<!-- START COPILOT ORIGINAL PROMPT -->
<details>
<summary>Original prompt</summary>
Start implementation
The user has attached the following file paths as relevant context:
- CLAUDE.md
<analysis>
[Chronological Review: The conversation began with the user requesting a
review of how updates on views are stored in the frontend of record page
layouts. The user expressed a desire to migrate heavy computations and
multiple requests from the frontend to the backend. They proposed
creating a custom upsertFieldsWidget mutation to handle updates
atomically. The conversation has focused on this migration strategy and
its implementation.]
[Intent Mapping: The user explicitly requested to "check how we store
updates on views in the frontend of record page layouts" and indicated a
need to "migrate this task to the backend" by creating a custom mutation
for atomic updates.]
[Technical Inventory: The conversation involves the following technical
concepts:
- Frontend and backend architecture for handling page layout updates.
- Custom GraphQL mutation (upsertFieldsWidget) for atomic updates.
- The file in focus is page-layout-update.service.ts, which is likely
responsible for handling updates related to page layouts in the
backend.]
[Code Archaeology: The specific file being worked on is
/Users/devessier/Web/twenty/packages/twenty-server/src/engine/metadata-modules/page-layout/services/page-layout-update.service.ts.
The user is likely looking to modify or enhance this service to
accommodate the new mutation for handling updates.]
[Progress Assessment: The user has identified the need for a backend
migration strategy but has not yet implemented any changes. The focus is
on planning the transition from frontend to backend processing.]
[Context Validation: All critical information for continuation is
captured, including the user's intent to migrate computations to the
backend and the specific file being modified.]
[Recent Commands Analysis: The last agent commands involved checking the
current file context and the user's request to summarize the
conversation. No specific tool results were generated as the focus was
on summarizing the conversation history. The agent was actively working
on summarizing the conversation when the token budget was exceeded,
which was triggered by the user's request for a summary.]
</analysis>
<summary>
1. Conversation Overview:
- Primary Objectives: The user requested to "check how we store updates
on views in the frontend of record page layouts" and expressed a desire
to "migrate this task to the backend" by creating a custom
upsertFieldsWidget mutation for atomic updates.
- Session Context: The conversation has focused on the need to shift
heavy computations from the frontend to the backend, with an emphasis on
implementing a new mutation to streamline updates.
- User Intent Evolution: The user's needs have remained consistent,
focusing on backend migration for efficiency and atomicity in updates.
2. Technical Foundation:
- Core Technology: The conversation involves a backend service likely
built with Node.js and TypeScript, as indicated by the file path.
- Framework/Library: The use of GraphQL for handling mutations is
implied through the mention of a custom mutation.
- Architectural Pattern: The user is looking to implement a
service-oriented architecture where the backend handles complex
computations instead of the frontend.
- Environment Detail: The current working file is located in a server
package of the twenty repository.
3. Codebase Status:
- File Name: page-layout-update.service.ts
- Purpose: This file is responsible for managing updates related to page
layouts in the backend.
- Current State: The user is considering modifications to implement a
new mutation for handling updates.
- Key Code Segments: Specific functions or classes have not been
detailed yet, as the focus is on planning changes.
- Dependencies: This service likely interacts with other components in
the metadata-modules related to page layouts.
4. Problem Resolution:
- Issues Encountered: The current challenge is the inefficiency of
handling updates in the frontend.
- Solutions Implemented: The proposed solution is to create a custom
mutation to handle updates atomically in the backend.
- Debugging Context: No ongoing troubleshooting efforts have been
mentioned yet.
- Lessons Learned: The need for backend processing to improve
performance has been highlighted.
5. Progress Tracking:
- Completed Tasks: No tasks have been completed yet; the user is in the
planning phase.
- Partially Complete Work: The user is preparing to implement a new
mutation for backend updates.
- Validated Outcomes: No features have been confirmed working through
testing at this stage.
6. Active Work State:
- Current Focus: The user is focused on modifying the
page-layout-update.service.ts to implement the new mutation.
- Recent Context: The last few exchanges involved discussing the
migration of update tasks from the frontend to the backend.
- Working Code: No specific code snippets have been modified yet, as the
co...
</details>
<!-- START COPILOT CODING AGENT SUFFIX -->
Created from [VS
Code](https://code.visualstudio.com/docs/copilot/copilot-coding-agent).
<!-- START COPILOT CODING AGENT TIPS -->
---
💡 You can make Copilot smarter by setting up custom instructions,
customizing its development environment and configuring Model Context
Protocol (MCP) servers. Learn more [Copilot coding agent
tips](https://gh.io/copilot-coding-agent-tips) in the docs.
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Devessier <29370468+Devessier@users.noreply.github.com>
Co-authored-by: Baptiste Devessier <baptiste@devessier.fr>
|
||
|
|
00209f7e2c |
Wire fields widget to backend + basic edition (#17965)
Closes https://github.com/twentyhq/core-team-issues/issues/2215 Closes https://github.com/twentyhq/core-team-issues/issues/2216 Closes https://github.com/twentyhq/core-team-issues/issues/2218 ## Fields widget edition demo https://github.com/user-attachments/assets/08626d70-8fcb-4ae2-9222-10ef57e75f90 ## Dashboards still work https://github.com/user-attachments/assets/aa9a9c45-a0a2-481e-b132-bc52254778da |
||
|
|
175df59c21 |
Support Skill in manifest (#18092)
# Introduction Support skill in manifest, pre-requisite for the twenty standard app migration |
||
|
|
6ad581d178 |
Fix global search for CJK and non-tokenizable text (#18030)
## Summary Fixes #12962 - Adds a two-pass ILIKE fallback to the global search service (`search.service.ts`) - **Fast path**: runs the tsvector query first (uses GIN index, sub-millisecond) - **Fallback**: only if tsvector returns fewer results than the limit, runs an ILIKE query on `searchVector::text` to catch cases where PostgreSQL's `simple` text search config fails to tokenize (continuous CJK text, etc.) - Zero performance impact for the common case (Latin text where tsvector works) - Also adds `escapeForIlike` utility to properly escape `%`, `_`, `\` in user input ### Why tsvector fails for CJK PostgreSQL's `simple` config treats continuous CJK text as a single lexeme: - `to_tsvector('simple', '示例商业线索')` → `'示例商业线索':1` - Searching `商业:*` only prefix-matches from the start, so it misses `商业` in the middle The ILIKE fallback catches these substring matches when the tsvector path can't. ### What this fixes - Global search (command menu / sidebar) - Relation picker (single and multi-object) - Morph relation picker All three use `search.service.ts` under the hood. Co-authored-by: mykh-hailo (original direction in #18021) ## Test plan - [ ] Search `示例` with records `示例商业线索` and `示例-商业-线索` → both should appear - [ ] Search `商业` → both should appear (previously only the hyphenated one did) - [ ] Search for Latin text (e.g. `john`) → same performance, results unchanged - [ ] Relation picker search with CJK text → results appear - [ ] Search input with special chars like `%` or `_` → no SQL injection, results correct Made with [Cursor](https://cursor.com) --------- Co-authored-by: mykh-hailo <mykh-hailo@users.noreply.github.com> Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
9e54a8082c |
Remove non positive integer constraint on Nav Menu Item (#18081)
To fit with position typed field logic + Ease favorite to nav menu item migration (which have negative and float position) |
||
|
|
88424611ec |
Refactor and standardize isSystem field and object (#17992)
# Introduction ## Centralize system field definitions - Extract a single `PARTIAL_SYSTEM_FLAT_FIELD_METADATAS` constant as the source of truth for all 8 system fields (`id`, `createdAt`, `updatedAt`, `deletedAt`, `createdBy`, `updatedBy`, `position`, `searchVector`), eliminating duplication across custom object and standard app field builders - Refactor `buildDefaultFlatFieldMetadatasForCustomObject` to use the shared constant via a new `buildObjectSystemFlatFieldMetadatas` helper ## Mark system fields as `isSystem: true` - Fields `id`, `createdAt`, `updatedAt`, `deletedAt`, `createdBy`, `updatedBy`, `position`, `searchVector` are now properly flagged as system fields across all standard objects and custom object creation - Standard app field builders for all ~30 standard objects updated to set `isSystem: true` on `createdAt`, `updatedAt`, `deletedAt`, `createdBy`, `updatedBy` - System-only standard objects (blocklist, calendar channels, message threads, etc.) now also include `createdBy`, `updatedBy`, `position`, `searchVector` field definitions that were previously missing ## Validate system fields on object creation - New transversal validation (`crossEntityTransversalValidation`) runs after all atomic entity validations in the build orchestrator, ensuring all 8 system fields are present with correct `type` and `isSystem: true` when an object is created - New `buildUniversalFlatObjectFieldByNameAndJoinColumnMaps` utility to resolve field names to universal identifiers for a given object - New exception codes: `MISSING_SYSTEM_FIELD` and `INVALID_SYSTEM_FIELD` on `ObjectMetadataExceptionCode` ## Protect system fields and objects from mutation - Field validators now block update/delete of `isSystem` fields by non-system callers (`FIELD_MUTATION_NOT_ALLOWED`) - Object validators now block update/delete of `isSystem` objects by non-system callers - `POSITION` and `TS_VECTOR` field type validators replaced: instead of rejecting creation outright, they now validate that the field is named correctly (`position` / `searchVector`) and has `isSystem: true` ## Distinguish `isSystemBuild` from `isCallerTwentyStandardApp` - New `isCallerTwentyStandardApp` utility checks whether the caller's `applicationUniversalIdentifier` matches the twenty standard app - Name-sync logic (`isFlatFieldMetadataNameSyncedWithLabel`, `areFlatObjectMetadataNamesSyncedWithLabels`) refactored to use `isCallerTwentyStandardApp` for custom suffix decisions, keeping `isSystemBuild` for mutation permission checks - `WorkspaceMigrationBuilderOptions` type updated to include `applicationUniversalIdentifier` ## Adapt frontend filtering - New `HIDDEN_SYSTEM_FIELD_NAMES` constant (`id`, `position`, `searchVector`) and `isHiddenSystemField` utility to only hide truly internal fields while keeping user-facing system fields (`createdAt`, `updatedAt`, `deletedAt`, `createdBy`, `updatedBy`) visible in the UI - ~20 frontend files updated to replace `!field.isSystem` checks with `!isHiddenSystemField(field)` across record index, settings, data model, charts, workflows, spreadsheet import, aggregations, and role permissions ## Add 1.19 upgrade commands - **`backfill-system-fields-is-system`**: Raw SQL command to set `isSystem = true` on existing workspace fields matching system field names, and fix `position` field type from `NUMBER` to `POSITION` for `favorite`/`favoriteFolder` objects. Includes proper cache invalidation. - **`add-missing-system-fields-to-standard-objects`**: Codegen'd workspace migration to create missing `position`, `searchVector`, `createdBy`, `updatedBy` fields on standard objects that didn't previously have them. Runs via `WorkspaceMigrationRunnerService` in a single transaction with idempotency check. **Known limitation**: assumes all standard objects exist and are valid in the target workspace. ## Add `universalIdentifier` for system fields in standard object constants - `standard-object.constant.ts` updated to include `universalIdentifier` for `createdBy`, `updatedBy`, `position`, and `searchVector` across all standard objects - `fieldManifestType.ts` updated to support the new field manifest shape ## System relation Completely removed and backfilled all `isSystem` relation to be false false As we won't require an object to have any relation system fields ## Add integration tests - New test suite `failing-sync-application-object-system-fields` covering: missing system fields, wrong field types (`id` as TEXT, `createdAt` as TEXT, `position` as TEXT), system field deletion attempts, and system field update attempts - New test utilities: `buildDefaultObjectManifest` (builds an object manifest with all 8 system fields) and `setupApplicationForSync` (centralizes application setup) - Existing successful sync test updated to verify system fields are created with correct properties ## Next step Make the builder scope the compared entity to be the currently built app + nor twenty standard app |
||
|
|
082400f751 |
Add objectRecordCounts query to /metadata endpoint (#18054)
## Summary - Adds an `objectRecordCounts` query on the `/metadata` GraphQL endpoint that returns approximate record counts for all objects in the workspace - Uses PostgreSQL's `pg_class.reltuples` catalog stats — a single instant query instead of N `COUNT(*)` table scans - Replaces the previous `CombinedFindManyRecords` approach which hit the server's 20 root resolver limit and silently showed 0 for all counts on the settings Data Model page ### Server - `ObjectRecordCountDTO` — GraphQL type with `objectNamePlural` and `totalCount` - `ObjectRecordCountService` — reads `pg_class` catalog for the workspace schema - Query added to `ObjectMetadataResolver` with `@MetadataResolver()` + `NoPermissionGuard` ### Frontend - `OBJECT_RECORD_COUNTS` query added to `object-metadata/graphql/queries.ts` - `useCombinedGetTotalCount` simplified to a zero-argument hook using the new query - `SettingsObjectTable` simplified to a single hook call --------- Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
c3781e87cc |
Sync page Layout (#18034)
## Sync page layouts, tabs, and widgets Adds the ability for SDK applications to synchronize `pageLayout`, `pageLayoutTab`, and `pageLayoutWidget` entities, following the same pattern established in #18003 for views and navigation menu items. ### Changes **`twenty-shared`** - New `PageLayoutManifest`, `PageLayoutTabManifest`, and `PageLayoutWidgetManifest` types with a hierarchical structure (page layout → tabs → widgets) - Added `pageLayouts: PageLayoutManifest[]` to the `Manifest` type **`twenty-sdk`** - New `definePageLayout()` SDK function with validation for universalIdentifier, name, and nested tabs/widgets - Wired into the manifest extraction and build pipeline (`DefinePageLayout` target function, `PageLayouts` entity key) - Exported from the SDK entry point **`twenty-server`** - Added `pageLayout`, `pageLayoutTab`, `pageLayoutWidget` to `APPLICATION_MANIFEST_METADATA_NAMES` - New conversion utilities: manifest → universal flat entity for all three entity types - Updated `computeApplicationManifestAllUniversalFlatEntity |
||
|
|
058489b5cc |
Fixes - Workspace logo migration (#18035)
- Update migration command to handle case where workspace logo is originated from workspace email and point to twenty-icons.com - Update same logic for new workspaces - Add feature-flag for all newly created workspaces |
||
|
|
7332379d26 |
Improve API Client usage and add Typescript check (#18023)
## Summary https://github.com/user-attachments/assets/1e75cc9d-d9d2-4ef2-99f9-34450f5d8de7 Add background incremental type checking (`tsc --watch`) to the SDK dev mode, so type regressions are caught when the generated API client changes — without requiring a full rebuild of source files. Previously, removing a field from the data model would regenerate the API client, but existing front components/logic functions referencing the removed field wouldn't surface type errors (since their source didn't change, esbuild wouldn't rebuild them). ## What changed - **Background `tsc --watch`**: a long-lived TypeScript watcher runs alongside esbuild watchers, incrementally re-checking all files when the generated client changes. Only logs on state transitions (errors appear / errors clear) to stay quiet. - **Atomic client generation**: API client is now generated into a temp directory and swapped in atomically, avoiding a race condition where `tsc --watch` could see an empty `generated/` directory mid-regeneration. - **Step decoupling**: orchestrator steps no longer receive `uploadFilesStep` directly. Instead, they use callbacks (`onFileBuilt`, `onApiClientGenerated`), and each step manages its own `builtFileInfos` state. - **`apiClientChecksum` omitted from `ApplicationConfig`**: it's a build-time computed value, same as `packageJsonChecksum`. <img width="327" height="177" alt="image" src="https://github.com/user-attachments/assets/02bd25bb-fa41-42b0-8d96-01c51bd4580c" /> <img width="529" height="452" alt="image" src="https://github.com/user-attachments/assets/61f6e968-365b-4a5b-8f2b-a8419d6b1bd3" /> |
||
|
|
08a3d983cb |
Date & DateTime validation fixes / improvements (#18009)
Fixes https://github.com/twentyhq/twenty/issues/17138 - Backend should have strict date/dateTime format validation - FE in import csv is more permissive --------- Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com> |
||
|
|
c0cc0689d6 |
Add Client Api generation (#17961)
## Add API client generation to SDK dev mode and refactor orchestrator into step-based pipeline ### Why The SDK dev mode lacked typed API client generation, forcing developers to work without auto-generated GraphQL types when building applications. Additionally, the orchestrator was a monolithic class that mixed watcher management, token handling, and sync logic — making it difficult to extend with new steps like client generation. ### How - **Refactored the orchestrator** into a step-based pipeline with dedicated classes: `CheckServer`, `EnsureValidTokens`, `ResolveApplication`, `BuildManifest`, `UploadFiles`, `GenerateApiClient`, `SyncApplication`, and `StartWatchers`. Each step has typed input/output/status, managed by a new `OrchestratorState` class. - **Added `GenerateApiClientOrchestratorStep`** that detects object/field schema changes and regenerates a typed GraphQL client (via `@genql/cli`) into `node_modules/twenty-sdk/generated` for seamless imports. - **Replaced `checkApplicationExist`** with `findOneApplication` on both server resolver and SDK API service, returning the entity data instead of a boolean. - **Added application token pair mutations** (`generateApplicationToken`, `renewApplicationToken`) to the API service, with the server now returning `ApplicationTokenPairDTO` containing both access and refresh tokens. - **Restructured the dev UI** into `dev/ui/components/` with dedicated panel, section, and event log components. - **Simplified `AppDevCommand`** from ~180 lines of watcher management down to ~40 lines that delegate entirely to the orchestrator. |
||
|
|
163c1175cb |
File - Migrate core pictures (workspace and member logo) + workflow attachments (#17924)
- Create a common file-by-id download controller - Create core picture module with resolver and logic to handle workspaceLogo and workspaceMemberProfilePicture update - Create workflow file module (same) - Data migration |
||
|
|
b4e924b671 |
Sync views and navigation items (#18003)
Both objects are necessary to fully enjoy objects within applications <img width="770" height="311" alt="Capture d’écran 2026-02-17 à 15 19 43" src="https://github.com/user-attachments/assets/48c51fa4-63f4-45b2-a40a-df73f3aa79be" /> |
||
|
|
347298902d |
Fix dashboard new tab creation (#17971)
## Before https://github.com/user-attachments/assets/cbc60013-8a5e-4af8-b02c-7dfbaf0c15e5 ## After https://github.com/user-attachments/assets/1fd6f9f7-e774-4c63-aa80-50b33d2a4c59 |
||
|
|
5544b5dcfe |
Fix and refactor all metadata relation (#17978)
# Introduction The initial motivation was that in the workspace migration create action some universal foreign key aggregators weren't correctly deleted before returned due to constant missconfiguration <img width="2300" height="972" alt="image" src="https://github.com/user-attachments/assets/9401eb02-2bb2-4e69-9c5f-9a354ff61079" /> It also meant that under the hood some optimistic behavior wasn't correctly rendered for some aggregators ## Solution Refactored the `ALL_METADATA_RELATIONS` as follows: This way we can infer the FK and transpile it to a universalFK, also the aggregators are one to one instead of one versus all available Making the only manual configuration to be defined the `foreignKey` and `inverseOneToManyProperty` ``` ┌──────────────────────────────────────┐ ┌─────────────────────────────────────────────┐ │ ALL_MANY_TO_ONE_METADATA_FOREIGN_KEY│ │ ALL_ONE_TO_MANY_METADATA_RELATIONS │ │──────────────────────────────────────│ │─────────────────────────────────────────────│ │ Derived from: Entity types │ │ Derived from: Entity types │ │ │ │ │ │ Provides: │ │ Provides: │ │ • foreignKey │ │ • metadataName │ │ │ │ • flatEntityForeignKeyAggregator │ │ Standalone low-level primitive │ │ • universalFlatEntityForeignKeyAggregator │ └──────────────┬───────────────────────┘ └──────────────┬──────────────────────────────┘ │ │ │ foreignKey type + │ inverseOneToManyProperty │ universalForeignKey derivation │ keys (type constraint) │ │ ▼ ▼ ┌───────────────────────────────────────────────────────────────┐ │ ALL_MANY_TO_ONE_METADATA_RELATIONS │ │───────────────────────────────────────────────────────────────│ │ Derived from: │ │ • Entity types (metadataName, isNullable) │ │ • ALL_MANY_TO_ONE_METADATA_FOREIGN_KEY (FK → universalFK) │ │ • ALL_ONE_TO_MANY_METADATA_RELATIONS (inverse keys) │ │ │ │ Provides: │ │ • metadataName │ │ • foreignKey (replicated from FK constant) │ │ • inverseOneToManyProperty │ │ • isNullable │ │ • universalForeignKey │ └──────────────────────────┬────────────────────────────────────┘ │ ┌──────────────────┼──────────────────┐ │ │ │ ▼ ▼ ▼ ┌───────────────────┐ ┌────────────────┐ ┌──────────────────────┐ │ Type consumers │ │ Atomic utils │ │ Optimistic utils │ │───────────────────│ │────────────────│ │──────────────────────│ │ • JoinColumn │ │ • resolve-* │ │ • add/delete flat │ │ • RelatedNames │ │ • get-* │ │ entity maps │ │ • UniversalFlat │ │ │ │ • add/delete │ │ EntityFrom │ │ │ │ universal flat │ │ │ │ │ │ entity maps │ └───────────────────┘ └────────────────┘ │ │ │ (bridge via │ │ inverseOneToMany │ │ Property → │ │ ONE_TO_MANY for │ │ aggregator lookup) │ └──────────────────────┘ ``` ### Previously ``` ┌─────────────────────────────────────────────────────────────────────┐ │ ALL_METADATA_RELATIONS │ │─────────────────────────────────────────────────────────────────────│ │ Derived from: Entity types │ │ │ │ Structure: { [metadataName]: { manyToOne: {...}, oneToMany: {...},│ │ serializedRelations?: {...} } } │ │ │ │ manyToOne provides: │ │ • metadataName │ │ • foreignKey │ │ • flatEntityForeignKeyAggregator (nullable, often wrong/null) │ │ • isNullable │ │ │ │ oneToMany provides: │ │ • metadataName │ │ │ │ Monolithic single source of truth │ └──────────────────────────┬──────────────────────────────────────────┘ │ │ manyToOne entries transformed via │ ToUniversalMetadataManyToOneRelationConfiguration │ ▼ ┌─────────────────────────────────────────────────────────────────────┐ │ ALL_UNIVERSAL_METADATA_RELATIONS │ │─────────────────────────────────────────────────────────────────────│ │ Derived from: ALL_METADATA_RELATIONS (type-level transform) │ │ │ │ Structure: { [metadataName]: { manyToOne: {...}, oneToMany: {...} │ │ } } │ │ │ │ manyToOne provides: │ │ • metadataName │ │ • foreignKey │ │ • universalForeignKey (derived: FK → replace Id → UniversalId) │ │ • universalFlatEntityForeignKeyAggregator (derived from │ │ flatEntityForeignKeyAggregator → replace Ids → UniversalIds) │ │ • isNullable │ │ │ │ oneToMany: passthrough from ALL_METADATA_RELATIONS │ │ │ │ Duplicated monolith with universal key transforms │ └──────────────────────────┬──────────────────────────────────────────┘ │ ┌──────────────────┼──────────────────────┐ │ │ │ ▼ ▼ ▼ ┌───────────────┐ ┌────────────────────┐ ┌──────────────────────┐ │ Type consumers│ │ Atomic utils │ │ Optimistic utils │ │───────────────│ │────────────────────│ │──────────────────────│ │ • JoinColumn │ │ • resolve-entity- │ │ • add/delete flat │ │ • RelatedNames│ │ relation-univ-id │ │ entity maps │ │ • Universal │ │ (ALL_METADATA_ │ │ (ALL_METADATA_ │ │ FlatEntity │ │ RELATIONS │ │ RELATIONS │ │ From │ │ .manyToOne) │ │ .manyToOne) │ │ │ │ │ │ │ │ Mixed usage │ │ • resolve-univ- │ │ • add/delete univ │ │ of both │ │ relation-ids │ │ flat entity maps │ │ constants │ │ (ALL_UNIVERSAL_ │ │ (ALL_UNIVERSAL_ │ │ │ │ METADATA_REL │ │ METADATA_REL │ │ │ │ .manyToOne) │ │ .manyToOne) │ │ │ │ │ │ │ │ │ │ • resolve-univ- │ │ universalFlatEntity │ │ │ │ update-rel-ids │ │ ForeignKeyAggregator │ │ │ │ (ALL_UNIVERSAL_ │ │ read directly from │ │ │ │ METADATA_REL │ │ the constant │ │ │ │ .manyToOne) │ │ │ │ │ │ │ │ │ │ │ │ • regex hack: │ │ │ │ │ │ foreignKey │ │ │ │ │ │ .replace(/Id$/, │ │ │ │ │ │ 'UniversalId') │ │ │ └───────────────┘ └────────────────────┘ └──────────────────────┘ ``` |
||
|
|
2b7b05de2e |
[OBJECT_MANIFEST_BREAKING_CHANGE] Sync returns workspace migration (#17918)
# Introduction In this PR we start returning a workspace migration post sync so it can committed and provided within the tarball ## Universal aggregators utils Created two utils ### deleteUniversalFlatEntityForeignKeyAggregators Used when building a universal create action, a newly created actions should not contain any aggregated foreign key so they won't be codegen in the workspace migration but also they are overriden at uninversal to flat transpilation anw ### resetUniversalFlatEntityForeignKeyAggregators Used before validating a new flat entity creation, some validator will consume the fk aggregator in order to validate integrity, but of optimstically provided it can result to errors. To avoid caller responsability we override them here ## create-field-action refactor Refactored the universal and flat field create action to be following the base actions in order to ease typing Also it was tailored to handle unlimited amount of flat field metadata in the same actions whereas in the reality we were always only sending at max 2 ( for relation fields ) Note: relation field has to be provided at the same as if not optimistic would fail to retrieve circular universal identifiers ## ObjectManifest Now always expect a `labelIdentifierFieldMetadataUniversalIdentifier` ## Integration test Created an integration test that creates an app, sync a first manifest and a second implying update workspace migration action generation |