- Updated font weight in AIChatThreadGroup for better theme consistency.
- Simplified isExpanded logic in MainNavigationDrawerTabsRow for clarity and improved readability.
- Adjusted rendering conditions for styled components to enhance responsiveness based on navigation drawer state.
- Added NAVIGATION_DRAWER_CLICK_OUTSIDE_ID to manage click outside events in the Navigation Drawer.
- Updated CommandMenuOpenContainer to include the new click outside ID for better interaction handling.
- Simplified shouldShowCloseButton logic in CommandMenuTopBar for clarity.
- Refactored MainNavigationDrawerTabsRow to improve chat thread creation handling and responsiveness.
- Updated MainNavigationDrawerTabsRow to conditionally render based on AI feature flag and mobile state.
- Modified MobileNavigationBar to include a new chat option when AI is enabled, improving user interaction.
- Cleaned up import statements for better organization and clarity.
- Introduced NavigationDrawerAIChatThreadsList to render chat threads grouped by date.
- Integrated AIChatSkeletonLoader for loading state representation.
- Utilized useAIChatThreadClick hook for handling thread selection.
- Enhanced NavigationDrawerItem to support always showing right options for better user interaction.
- Removed direct state management and logic from AIChatThreadGroup.
- Introduced useAIChatThreadClick hook to encapsulate thread click functionality.
- Improved code organization and readability by separating concerns.
- Updated MainNavigationDrawerTabsRow to conditionally render tabs based on navigation drawer expansion state.
- Modified NavigationDrawer to adjust its width based on the expanded state, improving user experience.
- Refactored NavigationDrawerHeader to adapt layout and controls based on the expanded state, ensuring consistent design.
- Introduced new props for styled components to handle dynamic styling based on the navigation drawer's state.
- Removed MainNavigationDrawerFixedItems component and replaced it with MainNavigationDrawerTabsRow for improved tab management.
- Introduced AIChatThreadsList within the MainNavigationDrawer for chat tab functionality.
- Added navigationDrawerActiveTabState to manage active tab state.
- Updated NavigationDrawerHeader to include a search button and adjusted styles for better layout consistency.
- Updated the conditional check for filtered color names to use `isNonEmptyString` for better clarity and functionality.
- Reorganized import statements for consistency.
- Reorganized import statements for consistency and clarity.
- Introduced `isDefined` utility for better handling of `selectedObjectMetadataItem`.
- Enhanced readability by adjusting the conditional check for icon color assignment.
- Renamed `setFolderPicker` to `openFolderPicker` for better readability.
- Updated references to `setFolderPicker` throughout the component to reflect the new naming.
- Reorganized import statements for consistency.
- Introduced DEFAULT_COLOR_LABELS constant for color labels in the navigation menu.
- Updated CommandMenuEditColorOption to use DEFAULT_COLOR_LABELS instead of hardcoded COLOR_LABELS.
- Adjusted MenuItemSelectColor to import DEFAULT_COLOR_LABELS from a new constants file for better organization and maintainability.
- Added IconColumnInsertRight to various components for consistent iconography.
- Updated navigation menu item hooks to utilize the new icon for adding items.
- Refactored CommandMenuPageInfo to include a layout for the new sidebar item page.
- Ensured consistent usage of CommandMenuPages type across relevant hooks.
- Updated CommandMenuEditColorOption to accept ThemeColor type for improved type safety.
- Replaced direct color prop usage with parseThemeColor in CommandMenuEditLinkItemView, CommandMenuEditObjectViewBase, and CommandMenuNavigationMenuItemEditPage for consistent color parsing.
- Reorganized import statements for better clarity and consistency.
- Updated the conditional check for filtered color names to use `isDefined` for improved readability.
- Renamed `selectedNavItemId` to `selectedNavigationItemId` to align with previous naming conventions and improve clarity.
- Adjusted conditional check to reflect the updated variable name.
## Description
- Add `isHeadless` field to `FrontComponent` entity so front components
can run without rendering UI in the command menu
- Introduce headless front component mounting logic:
`HeadlessFrontComponentMountRoot` at the application root,
`useMountHeadlessFrontComponent`, and `useUnmountHeadlessFrontComponent`
hooks to mount/unmount headless components
- Expand the SDK with new action components (`Action`, `ActionLink`,
`ActionOpenSidePanelPage`) and host communication functions
(`openSidePanelPage`, `unmountFrontComponent`)
- Move `CommandMenuPages` type to twenty-shared so the SDK can reference
it for side panel navigation
## Video QA
https://github.com/user-attachments/assets/4f9e3bb1-fcd1-42be-b3f4-a97e80c2add2
## Summary
- Fixes a flaky test in `simple-secret-encryption.util.spec.ts` that
fails ~1 in 256 runs
- AES-256-CBC doesn't guarantee wrong-key decryption throws — PKCS7
padding validation is probabilistic. When padding accidentally looks
valid, decryption silently returns garbage instead of throwing.
- Changed the test to verify the correct security property: wrong-key
decryption must never return the original secret (both throw and garbage
are acceptable)
- Audited both production `decryptSecret` call sites in
`two-factor-authentication.service.ts` — they always use the correct
key, so end users are not affected
## Test plan
- [x] Test passes 5/5 consecutive runs locally
- [x] Lint passes
Made with [Cursor](https://cursor.com)
Co-authored-by: Cursor <cursoragent@cursor.com>
- Updated WorkspaceNavigationMenuItems to conditionally set page titles based on item type, enhancing clarity in navigation.
- Introduced a new utility function, getLabelForItemType, in useSelectedNavigationMenuItemEditItemLabel to streamline label retrieval for different item types, improving code maintainability.
- Added a new prop `showColorOption` to CommandMenuEditObjectViewBase for controlling the display of color customization options based on the selected item.
- Updated CommandMenuNavigationMenuItemEditPage to determine when to show color options based on the selected item's view key.
- Introduced a new hook, `useObjectNavItemColor`, to retrieve the appropriate color for navigation menu items, improving visual consistency across components.
## Summary
- Consolidates the MCP server from two endpoints (`/mcp` +
`/mcp/metadata`) into a **single `POST /mcp`** endpoint exposing five
high-level tools: `get_tool_catalog`, `learn_tools`, `execute_tool`,
`load_skills`, and `search_help_center`
- Fixes **DATABASE_CRUD tools not accessible via API key auth** by
removing an unnecessary `userId`/`userWorkspaceId` guard in
`DatabaseToolProvider` and threading `ApiKeyWorkspaceAuthContext`
through the tool context chain
- Improves tool descriptions with **STEP 1/2/3 workflow guidance** so AI
clients follow the correct discovery flow (catalog → learn → execute)
instead of guessing tool names
- Simplifies the **frontend AI settings** by removing the schema picker
dropdown (no more "Core Schema" vs "Metadata Schema" choice)
## Changes
### Backend
- **Deleted**: `mcp-metadata.controller.ts`, `mcp-metadata.service.ts`
(merged into core)
- **New**: `get-tool-catalog.tool.ts` — browsable, categorized tool
discovery
- **Fixed**: `DatabaseToolProvider.generateDescriptors` — removed guard
that blocked API key access to CRUD tools
- **Fixed**: `ToolContext` type + `ToolRegistryService` — `authContext`
now flows through so API key CRUD execution works end-to-end
- **Updated**: `McpProtocolService` — builds
`ApiKeyWorkspaceAuthContext` for API key requests
- **Updated**: All MCP tool descriptions with explicit step numbering
### Frontend
- **Simplified**: `SettingsAIMCP.tsx` — removed schema selector
dropdown, shows single MCP config
## Test plan
- [x] All 19 MCP unit tests pass (controller + protocol service)
- [x] Server and frontend lint clean
- [x] Server and frontend typecheck pass
- [x] Live-tested on localhost:3000 with API key: `get_tool_catalog`
returns 236 tools including 196 DATABASE_CRUD tools
- [x] Live-tested `execute_tool` with `find_companies` via API key —
returns real data
- [x] Tested MCP connection from Cursor IDE via project-level
`.cursor/mcp.json`
Made with [Cursor](https://cursor.com)
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
## 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>
## Automated fix for [bug None](https://sonarly.com/issue/None?type=bug)
**Severity:** `critical`
### Summary
When uploading a person avatar with the new FILES field migration
enabled, the optimistic record validation in
computeOptimisticRecordFromInput throws because the avatarFile field may
not be recognized in the person object metadata, crashing the upload
flow.
### User Impact
Users with the IS_FILES_FIELD_MIGRATED feature flag enabled cannot
upload or change a person's avatar photo. The upload operation throws an
unhandled error, preventing the avatar update from completing.
### Root Cause
The usePersonAvatarUpload hook passes avatarFile as a field in
updateOneRecordInput when calling updateOneRecord. This input goes
through computeOptimisticRecordFromInput, which validates that every
field in the record input exists in objectMetadataItem.fields. The
avatarFile field (type FILES) was recently added as a standard field on
the person object, but may not yet be present in the frontend's cached
object metadata for all workspaces (e.g., workspaces where the metadata
has not been synced after the migration, or SSE events arriving before
metadata refresh). When avatarFile is not found in the metadata fields
array, the validation throws. The useUpdateOneRecord hook supports an
optimisticRecord parameter that bypasses this validation entirely, but
usePersonAvatarUpload did not provide it.
Introduced by Etienne in commit d0c1841f0f on 2026-02-11, which added
the avatar file migration and upload logic but did not supply an
optimisticRecord to bypass the strict field validation in
computeOptimisticRecordFromInput.
**Introduced by:** Etienne on 2026-02-11 in commit
[`d0c1841`](https://github.com/twentyhq/twenty/commit/d0c1841f0f8a6a9069bbe2e9cafacf4ff6137f82)
### Suggested Fix
Pass an explicit optimisticRecord parameter when calling updateOneRecord
from usePersonAvatarUpload. The useUpdateOneRecord hook uses
optimisticRecord via nullish coalescing (optimisticRecord ??
computeOptimisticRecordFromInput(...)), so providing it bypasses the
strict field validation in computeOptimisticRecordFromInput entirely.
The avatarFile value is extracted into a shared variable to avoid
duplication between updateOneRecordInput and optimisticRecord.
---
*Generated by [Sonarly](https://sonarly.com)*
---------
Co-authored-by: Sonarly Claude Code <claude-code@sonarly.com>
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Etienne <etiennejouan@users.noreply.github.com>
## Summary
- The `listPlans` GraphQL query was returning metered tier `upTo` values
in internal credit units (1000x the display value), causing "Credits by
period" and "Credit Plan" dropdowns to show "50M" instead of "50k"
- Applied the existing `INTERNAL_CREDITS_PER_DISPLAY_CREDIT` (1000)
divisor in `formatBillingDatabasePriceToMeteredPriceDTO`, matching the
conversion already used in `getMeteredProductsUsage` resolver
- Updated frontend mock data to reflect the corrected display-unit
values
## Test plan
- [x] Unit tests pass for `format-database-product-to-graphql-dto.util`
- [x] Unit tests pass for `metered-credit.service` and
`billing-credit-rollover.service`
- [ ] Verify billing page shows correct credit amounts (50k, not 50M)
for "Credits by period" and "Credit Plan"
Made with [Cursor](https://cursor.com)
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
- Introduced a new hook, useOpenAddItemToFolderPage, to manage navigation for adding items to a folder.
- Updated CommandMenuNavigationMenuItemEditPage to include an option for adding items to a folder, enhancing user interaction.
- Refactored WorkspaceNavigationMenuItemsFolder to utilize the new hook for streamlined item addition.
- Improved the organization of imports and component logic for better maintainability.
- Added logic to navigate to the edit page of a navigation menu item if in edit mode and a selected item is defined.
- Maintained existing functionality for navigating to the root command menu when not in edit mode.
- Imported MAIN_COLOR_NAMES for dynamic color handling.
- Added logic to generate colors for custom objects based on a hash of their names.
- Updated getStandardObjectIconColor to return a color for custom objects when not found in the standard list.
- Added flex display and center alignment to the search container for better layout consistency.
- Adjusted padding to use inline spacing for a more responsive design.
- Increased height of the search container to enhance usability.
- Integrated dynamic icon and color handling for folders in CommandMenuEditFolderPickerSubView.
- Updated folder data structure in related hooks to include icon and color properties for improved visual representation.
- Refactored folder rendering logic to utilize new icon and color attributes, enhancing user experience.
- Added a label for the folder in CommandMenuFolderInfo.
- Capitalized the label for link in CommandMenuLinkInfo.
- Refactored label logic in CommandMenuObjectViewRecordInfo to utilize selected item metadata and improved view handling.
### What this PR do ?
Stops the delay fields from updating the workflow on every keystroke by
keeping values locally and saving them on blur, which fixes the cached
relation error
Fixed#15709
---------
Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
- Introduced StyledMenuStyleText for consistent styling of color labels.
- Updated RightComponent to use the new styled component, enhancing visual presentation.
## Context
Command to backfill record page layouts and related entities for legacy
workspaces.
## Test
Set SHOULD_SEED_STANDARD_RECORD_PAGE_LAYOUTS=false, reset DB then run
the command and compare with Set
SHOULD_SEED_STANDARD_RECORD_PAGE_LAYOUTS=true on a different workspace
## Summary
- **Consolidate logic function services**: Remove
`LogicFunctionMetadataService` and consolidate all logic function CRUD
operations into `LogicFunctionFromSourceService`, with a new
`LogicFunctionFromSourceHelperService` for shared validation/migration
logic
- **Introduce typed conversion utils following the skill pattern**: Add
`fromCreateLogicFunctionFromSourceInputToUniversalFlatLogicFunctionToCreate`
and `fromUpdateLogicFunctionFromSourceInputToFlatLogicFunctionToUpdate`
that convert DTO inputs directly to flat entities
(`UniversalFlatLogicFunction` / `FlatLogicFunction`), replacing the
previous intermediate `UpdateLogicFunctionMetadataParams` indirection
- **Simplify `CodeStepBuildService`**: Remove ~100 lines of manual
duplication logic by delegating to
`LogicFunctionFromSourceService.duplicateOneWithSource`
- **Remove completed 1-17 migration**: Delete
`MigrateWorkflowCodeStepsCommand` and associated utils that migrated
workflow code steps from serverless functions to logic functions
## Summary
- Upgrades `@swc/core` from 1.13.3 to **1.15.11** (swc_core v56), which
introduces CBOR-based plugin serialization replacing rkyv, eliminating
strict version-matching between SWC core and Wasm plugins
- Upgrades `@lingui/swc-plugin` from ^5.6.0 to **^5.11.0** (swc_core
50.2.3, built with `--cfg=swc_ast_unknown` for cross-version
compatibility)
- Upgrades `@swc/plugin-emotion` from 10.0.4 to **14.6.0** (swc_core 53,
also with backward-compat feature)
- Upgrades companion packages: `@swc-node/register` 1.8.0 → 1.11.1,
`@swc/helpers` ~0.5.2 → ~0.5.18, `@vitejs/plugin-react-swc` 3.11.0 →
4.2.3
### Why this is safe now
Starting from `@swc/core v1.15.0`, SWC replaced the rkyv serialization
scheme with CBOR (a self-describing format) and added `Unknown` AST enum
variants. Plugins built with `swc_core >= 47` and
`--cfg=swc_ast_unknown` are now forward-compatible across `@swc/core`
versions. Both `@lingui/swc-plugin@5.10.1+` and
`@swc/plugin-emotion@14.0.0+` have this support, meaning the old
version-matching nightmare between Lingui and SWC is largely solved.
Reference: https://github.com/lingui/swc-plugin/issues/179
## Test plan
- [x] `yarn install` resolves without errors
- [x] `npx nx build twenty-shared` succeeds
- [x] `npx nx build twenty-ui` succeeds (validates
@swc/plugin-emotion@14.6.0)
- [x] `npx nx typecheck twenty-front` succeeds
- [x] `npx nx build twenty-front` succeeds (validates vite + swc +
lingui pipeline)
- [x] `npx nx build twenty-emails` succeeds (validates lingui plugin)
- [x] Frontend jest tests pass (validates @swc/jest +
@lingui/swc-plugin)
- [x] Server jest tests pass (validates server-side SWC + lingui)
Made with [Cursor](https://cursor.com)
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
## Description
- Adds support for declaring command menu items directly within
`defineFrontComponent` via an optional command config property
- Introduces a new `CommandMenuItemManifest` type in twenty-shared and
wires it through the manifest build pipeline
## Example Of usage
```tsx
import { defineFrontComponent } from "twenty-sdk";
const TestAction = () => {
return <div>Test Action</div>;
};
export default defineFrontComponent({
universalIdentifier: "6c289461-0007-4a62-a99f-69e5c11a4ce7",
name: "test-action",
description: "Test Action",
component: TestAction,
command: {
universalIdentifier: "c07df864-495f-46f3-9f5b-9d3ce2589e9b",
label: "Run My Action",
icon: "IconBolt",
isPinned: false,
},
});
```
## Video QA
https://github.com/user-attachments/assets/f910fc6a-44a9-45d1-87c5-f0ce64bb3878
Managed to create a many to many app with minimal instructions. File
will need to be enriched with more pitfalls.
---------
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
# Introduction
While preparing the twenty-standard as code migration to twenty-app
through sdk I've faced permanent field enum update as the id was
generated dynamically at each twenty standard app construction
Making them deterministic in order to avoid having this noise
Won't backfill this on existing workspace as it's not critical and that
we will rework the options in the future
# Introduction
Atomically create the field and object to be created
And avoid synchronizing unrelated non up to date object and fields
Followup https://github.com/twentyhq/twenty/pull/17398
- Updated the structure of STANDARD_NAVIGATION_MENU_ITEMS by rearranging the order of items and correcting their universal identifiers and view references.
- Ensured that all items are consistently positioned to enhance navigation experience.
Introduced an error message on twenty-front CI earlier to try and inform
the user that test failure could be a coverage issue if no individual
test was failing. However, it led to the assumption that it must be
coverage failure in all cases even when it was test failure leading to
the CI being red.
This PR reverts the change.
- Replaced null key with ViewKey.INDEX in both computeStandardWorkflowRunViews and computeStandardWorkflowVersionViews functions to enhance consistency in view key handling.
- Updated imports to use the new utility for getting navigation menu item icon styles based on color.
- Introduced constants for default icon color and color shades to enhance maintainability.
- Removed deprecated utility functions and streamlined color handling across components.
# 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
- Replaced nullish coalescing with default value assignment for objectNameSingular, enhancing readability.
- Simplified logic for determining view overlay visibility and icon usage, improving maintainability.
- Updated color handling to ensure consistent application of styles based on icon presence, aligning with recent updates in navigation components.
- Replaced isNonEmptyString with isDefined for color validation, enhancing clarity and consistency in color handling.
- This change aligns with recent updates in color management across navigation components, improving maintainability.
- Simplified the assignment of properties in the useSaveNavigationMenuItemsDraft hook by removing unnecessary nullish coalescing, enhancing code readability.
- Updated change detection logic to directly compare values, improving maintainability and consistency in handling navigation menu item drafts.
- Moved the import of v4 from 'uuid' to improve code organization.
- Simplified color assignment logic by directly assigning the color variable, enhancing clarity and maintainability in the navigation menu draft management.
- Moved the import of DEFAULT_NAVIGATION_MENU_ITEM_COLOR_FOLDER to improve code organization.
- Updated the color determination logic to use isDefined instead of isNonEmptyString for better validation of folder colors, enhancing maintainability and consistency in color handling across navigation components.
- Changed ViewIcon color from gray11 to gray10 to enhance visual consistency with the theme. This minor adjustment aligns with recent updates in color handling across navigation components.
- Updated StyledViewOverlay to utilize theme.spacing for height and width, enhancing consistency with the theme.
- Adjusted ViewIcon size to use theme.spacing for improved maintainability and alignment with design standards.
- Refactored the StyledIconSlot to utilize theme.spacing for height and width when $hasFixedSize is true, enhancing consistency with the theme and improving maintainability.
## 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>
with new 'file' FILES field on attachment, UI should display attachment
name from file field value.
Issue with API users updating only 'file' FILES field (and not name
field anymore)
- Introduced a new optional 'color' property in the NavigationMenuItemManifest type to enhance customization of navigation items.
- Updated the fromNavigationMenuItemManifestToUniversalFlatNavigationMenuItem utility function to include the 'color' property, allowing for better visual representation in the navigation menu.
These changes improve the flexibility and visual consistency of navigation items across the application.
- Replaced DEFAULT_NAVIGATION_MENU_ITEM_COLOR_OBJECT constant with a hardcoded fallback color 'gray' for improved clarity in color handling.
- Streamlined the color determination process to enhance maintainability and consistency across navigation items.
These changes contribute to a more robust and straightforward approach to color management in the navigation drawer component.
- Replaced inline color determination logic with the getEffectiveNavigationMenuItemColor utility for improved clarity and maintainability.
- Removed unused default color constants to streamline the code.
These changes enhance the robustness of color handling for navigation menu items, aligning with recent updates in icon management.
- Updated icon theme color logic to utilize isNonEmptyString for better validation of navigation menu item colors.
- Integrated getStandardObjectIconColor and DEFAULT_NAVIGATION_MENU_ITEM_COLOR_OBJECT to ensure fallback options are available for icon rendering.
These changes improve the robustness of color handling for navigation items, enhancing visual consistency across the application.
- Introduced getEffectiveNavigationMenuItemColor utility to determine the appropriate color for navigation menu items based on their type and custom color settings.
- Updated CurrentWorkspaceMemberNavigationMenuItems and CurrentWorkspaceMemberOrphanNavigationMenuItems components to utilize the new color utility for icon rendering.
- Refactored NavigationMenuItemIcon to enhance icon styling logic based on effective color.
These changes improve the visual consistency and customization of navigation menu items across the application.
This PR adds Message folder association for message channel messages,
Currently under testing phase, not ready yet.
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
- Added useTheme and useIsFeatureEnabled hooks to manage icon rendering based on theme and feature flags.
- Improved icon rendering logic to conditionally display icons based on navigation menu item editing status.
- Streamlined code for better readability and maintainability.
These changes align with recent updates in icon management and enhance the flexibility of the RecordIndexPageHeaderIcon component.
- Introduced the usePlainIcon prop in NavigationDrawerItem to allow for simplified icon rendering.
- Updated NavigationDrawerSectionForWorkspaceItems to utilize the new usePlainIcon prop, enhancing customization options for menu items.
These changes improve the flexibility and visual consistency of the navigation drawer components.
- Replaced the previous icon rendering logic with the new RecordIndexPageHeaderIcon component, enhancing clarity and maintainability.
- Removed unused imports and streamlined the code for better readability.
These changes align with recent improvements in icon management within the application.
- Updated CommandMenuNewSidebarItemViewObjectPickerSubView and CommandMenuObjectPickerSubView to use NavigationMenuItemStyleIcon for icon rendering, enhancing visual consistency.
- Refactored CommandMenuObjectMenuItem to streamline icon color handling and improve readability by utilizing a styledIcon function.
These changes enhance the maintainability and clarity of the command menu components.
- Eliminated the import of ViewKey from both computeStandardWorkflowRunViews and computeStandardWorkflowVersionViews utilities, as it was no longer needed.
- Updated the key property in the views to null, simplifying the view definitions.
These changes streamline the code and improve clarity in the workflow view utilities.
## Summary
- **File storage (LocalDriver):** Add realpath resolution and symlink
rejection to `writeFile`, `downloadFile`, and `downloadFolder` — brings
them in line with the existing `readFile` protections. Includes unit
tests.
- **JWT:** Pin signing/verification to HS256 explicitly.
- **Auth:** Revoke active refresh tokens when a user changes their
password.
- **Logic functions:** Validate `handlerName` as a safe JS identifier at
both DTO and runtime level, preventing injection into the generated
runner script.
- **User entity:** Remove `passwordHash` from the GraphQL schema
(`@Field` decorator removed, column stays).
- **Query params:** Use `crypto.randomBytes` instead of `Math.random`
for SQL parameter name generation.
- **Exception filter:** Mirror the request `Origin` header instead of
sending `Access-Control-Allow-Origin: *`.
## Test plan
- [x] `local.driver.spec.ts` — writeFile rejects symlinks, downloadFile
rejects paths outside storage
- [ ] Verify JWT auth flow still works (login, token refresh)
- [ ] Verify password change invalidates existing sessions
- [ ] Verify logic function creation with valid/invalid handler names
- [ ] Verify file upload/download in dev environment
Made with [Cursor](https://cursor.com)
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
- Updated the CommandMenuObjectMenuItem component to improve readability by formatting the icon color retrieval.
- Removed the unused useTheme import from NavigationDrawerSectionForWorkspaceItems, streamlining the code.
These changes enhance code clarity and maintainability in the navigation menu components.
- Integrated useIsFeatureEnabled hook to conditionally render icon color based on the IS_NAVIGATION_MENU_ITEM_EDITING_ENABLED feature flag.
- Updated icon rendering logic in NavigationDrawerItem to enhance customization based on feature availability.
These changes improve the flexibility of the navigation menu by allowing dynamic icon color handling based on feature flags.
Issue : With IS_NAVIGATION_MENU_ITEM_ENABLED:true +
IS_NAVIGATION_MENU_ITEM_EDITING_ENABLED:false, nav menu design is
changed after 1.18.0 release : views expansion removed, system object
displayed, position re-ordered
We prefer keeping the same "old" favorite behaviour and design state
- After 1.18.0 all workspaces have up-to-date navigation menu items
(migrated)
- IS_NAVIGATION_MENU_ITEM_EDITING_ENABLED becomes the FF for nav menu
new design
---------
Co-authored-by: Charles Bochet <charles@twenty.com>
When a user's role lacks read permission on the target object (e.g.,
Company) or the intermediate junction object (e.g., EmploymentHistory),
junction relation fields like "Previous Companies" displayed as blank
instead of showing "Not shared."
- In RecordFieldList, junction fields now check the junction object's
read permission and set isForbidden on the field context so FieldDisplay
renders "Not shared" instead of an empty field.
- In RelationFromManyFieldDisplay, if junction records exist but all
nested target records are null (permission-denied by the API), the
component renders "Not shared" instead of an empty list.
---------
Co-authored-by: Félix Malfait <felix@twenty.com>
## Refactor page layout widget types into shared package and expose from
SDK
### Why
Widget configuration types were defined only on the server, forcing SDK
consumer apps to import from deep internal `twenty-shared/dist` paths —
fragile and breaks on structural changes. Server DTOs also had no
compile-time guarantee they matched the canonical types.
### What changed
- **`twenty-shared`**: Migrated `ChartFilter`, `GridPosition`,
`RatioAggregateConfig` and all 20 widget configuration variants into
`twenty-shared/types`. `PageLayoutWidgetConfiguration` (base, with
`SerializedRelation`) and `PageLayoutWidgetUniversalConfiguration`
(derived via `FormatRecordSerializedRelationProperties`) are now the
single source of truth.
- **`twenty-sdk`**: Re-exported `AggregateOperations`,
`ObjectRecordGroupByDateGranularity`, `PageLayoutTabLayoutMode`, and
`PageLayoutWidgetUniversalConfiguration` so consumer apps import from
`twenty-sdk` directly.
- **`twenty-server`**: All widget DTOs now `implements` their shared
type for compile-time enforcement. Added helpers to convert nested
`fieldMetadataId` ↔ `fieldMetadataUniversalIdentifier` inside chart
filters. Removed redundant local type re-exports.
Updating rich app so it also create:
- a many to many relation
- views
- navigation items
The app was built successfully.
Will still be missing front component examples
<img width="1498" height="660" alt="Capture d’écran 2026-02-18 à 17 47
25"
src="https://github.com/user-attachments/assets/acd5193f-3a36-4eb7-8276-3154e4e60f5e"
/>
## 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
- add a new optional key `postInstallLogicFunctionUniversalIdentifier`
in applicationConfig
- seed postInstall function in create-twenty-app
- update execute:function options
- update doc
- 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
## Summary
- Replace the character-stripping approach (`removeSqlDDLInjection`)
with standard PostgreSQL `escapeIdentifier` and `escapeLiteral`
functions across all workspace schema manager services
- Add missing identifier escaping to `createForeignKey` (was the only
method in the FK manager without it)
- Add allowlist validation for index WHERE clauses and FK action types
- Harden tsvector expression builder with proper identifier quoting
## Context
The workspace schema managers build DDL dynamically from metadata (table
names, column names, enum values, etc.). The previous approach stripped
all non-alphanumeric characters — safe but lossy (silently corrupts
values with legitimate special characters). The new approach uses
PostgreSQL's standard escaping:
- **Identifiers**: double internal `"` and wrap → `"my""table"` (same
algorithm as `pg` driver's `escapeIdentifier`)
- **Literals**: double internal `'` and wrap → `'it''s a value'` (same
algorithm as `pg` driver's `escapeLiteral`)
`removeSqlDDLInjection` is kept only for name generation (e.g.,
`computePostgresEnumName`) where stripping to `[a-zA-Z0-9_]` is the
correct behavior.
## Files changed
| File | What |
|------|------|
| `remove-sql-injection.util.ts` | Added `escapeIdentifier` +
`escapeLiteral` |
| `validate-index-where-clause.util.ts` | New — allowlist for partial
index WHERE clauses |
| 5 schema manager services | Replaced strip+manual-quote with
`escapeIdentifier`/`escapeLiteral` |
| `build-sql-column-definition.util.ts` | `escapeIdentifier` for column
names, validated `generatedType` |
| `sanitize-default-value.util.ts` | `escapeLiteral` instead of
stripping |
| `serialize-default-value.util.ts` | `escapeLiteral` for values,
`escapeIdentifier` for enum casts |
| `get-ts-vector-column-expression.util.ts` | `escapeIdentifier` for
field names in expressions |
| `sanitize-default-value.util.spec.ts` | Updated tests for escape
behavior |
## Test plan
- [x] All 64 existing tests pass across 6 test suites
- [x] `lint:diff-with-main` passes
- [x] TypeScript typecheck — no new errors
- [ ] Verify workspace sync-metadata still works end-to-end
- [ ] Verify custom object/field creation works
- [ ] Verify enum field option changes work
Made with [Cursor](https://cursor.com)
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
## Summary
- Replace all generic `"Unknown error"` fallback messages across the
server codebase with messages that include the actual error details
- The most impactful change is in `guard-redirect.service.ts`, which
handles OAuth redirect errors — non-`AuthException` errors (e.g.,
passport state verification failures) now show `"Authentication error:
<actual message>"` instead of the opaque `"Unknown error"`
- Gmail/Google error handler services now include the error message in
the thrown exception instead of discarding it
- Other catch blocks (workflow delay resume, migration runner rollback,
code interpreter, marketplace) now use `String(error)` for non-Error
objects instead of a static fallback
Fixes the class of issues reported in
https://github.com/twentyhq/twenty/issues/17812, where a user saw
"Unknown error" during Google OAuth and had no way to diagnose the root
cause (which turned out to be a session cookie / SSL configuration
issue).
## Test plan
- [ ] Verify OAuth error flows (e.g., Google Auth with misconfigured
callback URL) now display the actual error message on the `/verify` page
instead of "Unknown error"
- [ ] Verify Gmail sync error handling still correctly classifies and
re-throws errors with descriptive messages
- [ ] Verify workflow delay resume failures include the error details in
the workflow run status
Made with [Cursor](https://cursor.com)
Co-authored-by: Cursor <cursoragent@cursor.com>
- Introduced DEFAULT_NAVIGATION_MENU_ITEM_COLOR_FOLDER and DEFAULT_NAVIGATION_MENU_ITEM_COLOR_LINK constants for standardized color management.
- Updated AddToNavigationDragHandle, CurrentWorkspaceMemberNavigationMenuItems, NavigationMenuItemIcon, and WorkspaceNavigationMenuItemsFolder components to utilize the new color constants.
- Refactored icon color logic to improve consistency and maintainability across navigation menu items.
These changes enhance visual consistency and customization options in the navigation menu.
This fixes two edge cases for Gmail
- When policy was set to `SELECTED_FOLDERS` excluding root INBOX, it
missed label changes, so messages with newly applied labels weren't
imported until a full resync.
- Gmail thread replies by default by default do no inherit parent
message's label properties so thread context was also lost because only
individually labeled messages were returned, dropping earlier parts of
the conversation.
Fixed by subscribing to `labelAdded`/`labelRemoved` history events and
fetching full thread context when at least one message in a thread
carries a synced label. `ALL_FOLDERS` path is untouched.
## 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"
/>
Create the necessary tooling to listen to metadata events and plug it to
the front components. Now we have a hot reload like experience when we
edit a component in an app.
## Backend
- Split `EventWithQueryIds` into `ObjectRecordEventWithQueryIds` and
`MetadataEventWithQueryIds`
- Publish metadata event batches to active SSE streams in
`MetadataEventsToDbListener`
## Frontend
- Create a metadata event dispatching pipeline: SSE metadata events are
grouped by metadata name, transformed into
`MetadataOperationBrowserEventDetail` objects, and dispatched as browser
`CustomEvents`
- Add `useListenToMetadataOperationBrowserEvent` hook for consuming
metadata operation events filtered by metadata name and operation type
- Rename `useListenToObjectRecordEventsForQuery` to
`useListenToEventsForQuery`, now accepting both
`RecordGqlOperationSignature` and `MetadataGqlOperationSignature`
- Implement `useOnFrontComponentUpdated` which subscribes to front
component metadata events and updates the Apollo cache when the
component is modified
- Add `builtComponentChecksum` to the front component query and appends
it to the component URL for browser cache invalidation
- Imported NavigationMenuItemStyleIcon and getStandardObjectIconColor to enhance icon color handling.
- Refactored icon rendering logic to use a new PageHeaderIcon component that applies the standardized color.
- Improved code readability and maintainability by clarifying icon management within the header.
These changes align with recent enhancements in icon color support across navigation components.
## Context
Introducing "NewFieldDefaultConfiguration" to FIELDS widget
configurations
```typescript
{
isVisible: boolean;
viewFieldGroupId: string | null;
}
```
This configuration will define where a new field should be added (which
section) and its default visibility inside FIELDS widget views.
The new field position should always be at the end (meaning the last
position for the view fields OR the last position of a viewFieldGroup)
See "New fields" on this screenshot
<img width="401" height="724" alt="Layout V1"
src="https://github.com/user-attachments/assets/4969bcaa-f244-4504-8947-778a02c24c47"
/>
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>
- Added a search input to filter color options based on user input, enhancing usability.
- Introduced state management for the search value and updated the rendering logic to display filtered color names.
- Included a separator for better visual organization in the dropdown menu.
These changes improve the user experience by allowing quick access to specific colors in the command menu.
- Integrated a new feature to allow users to add menu items directly within the folder component.
- Implemented a context state for managing the insertion of new menu items.
- Added a button for adding items, which triggers a command menu for item creation.
- Updated folder content length calculation to accommodate the new item.
These changes improve user interaction and streamline the process of managing navigation menu items.
- Introduced a new helper function, getColorFromTheme, to streamline the retrieval of theme colors with customizable shades.
- Updated getNavigationMenuItemIconBorderColor to utilize the new helper function, enhancing consistency in color retrieval.
- Modified getNavigationMenuItemIconStyleFromColor to use the new helper for background, icon, and border colors, improving code readability and maintainability.
These changes enhance the flexibility and clarity of color management in navigation menu items.
- Fixed "property entity not found" error when updating/creating a new
field and querying the same object repository just after
- Downgraded log type for unnecessary migration
- Changed the color for the allOpportunities menu item from 'tomato' to 'red' to enhance visual consistency across the navigation menu.
This update aligns with recent enhancements to icon color support in navigation components.
- Integrated getStandardObjectIconColor utility across various components to standardize icon colors based on object type.
- Updated CommandMenuNewSidebarItemObjectFlow and CommandMenuNewSidebarItemViewPickerSubView to utilize the new color utility.
- Modified AddToNavigationDragHandle and hooks to accept and apply icon color.
- Added iconColor property to AddToNavigationDragPayloadObject for better customization.
These changes improve visual consistency and customization options in the navigation menu.
- Introduced getStandardObjectIconColor function to standardize icon colors based on object type.
- Updated CommandMenuNewSidebarItemViewObjectPickerSubView, CommandMenuNewSidebarItemViewSystemSubView, CommandMenuObjectMenuItem, and CommandMenuObjectPickerItem components to utilize the new color utility.
This enhances visual consistency across the command menu items.
- Introduced folderColor prop to allow customization of icon color.
- Updated NavigationDrawerSectionForWorkspaceItems to pass color from item if available.
This enhances the visual customization options for navigation menu items.
- Simplified StyledViewOverlay by removing the border color prop.
- Updated dimensions for the overlay box and icon sizes to fixed pixel values.
- Adjusted the background color to use a theme color directly.
This improves consistency and readability in the component's styling.
- Moved Apollo import to the top of the file for consistency.
- Removed duplicate type definitions for NavigationMenuItemFieldsFragment and related queries/mutations to streamline the code.
## 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.
Long overdue PR: replacing deprecated country code in workflows.
Will allow to use variables.
We keep storing the country code since it allows to display the right
flag in picker when there are multiple countries for one calling code.
But we do not store country for variables.
<img width="477" height="254" alt="Capture d’écran 2026-02-17 à 17 25
23"
src="https://github.com/user-attachments/assets/dc67c41c-33cf-4021-b7bb-490827b2aa3c"
/>
## Summary
- Refactors SSRF protection from a request-level adapter to
connection-level agents, validating resolved IPs in `createConnection` +
socket `lookup` events
- Sets both `httpAgent` and `httpsAgent` so validation applies
regardless of protocol switches during redirects
- Caps `maxRedirects` to 10 as defense in depth
## Test plan
- [x] All 59 existing + new unit tests pass (agent util, isPrivateIp,
service)
- [x] No linter errors
- [ ] Verify webhook delivery still works with URLs that redirect
- [ ] Verify image upload from external URLs still works (relies on
redirect following)
Made with [Cursor](https://cursor.com)
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> **Medium Risk**
> Changes core outbound HTTP security behavior and redirect handling,
which could impact webhook/image-fetch flows and connection semantics
despite improved SSRF coverage.
>
> **Overview**
> Refactors outbound SSRF protection from a custom axios `adapter` to
connection-level `httpAgent`/`httpsAgent` created by new
`createSsrfSafeAgent`, which blocks private IP literals up front and
validates DNS-resolved IPs via the socket `lookup` event.
>
> When safe mode is enabled, `SecureHttpClientService.getHttpClient` now
always installs both agents and enforces a capped `maxRedirects`
(default `5`), and the old `getSecureAxiosAdapter`
implementation/tests/types are removed. `isPrivateIp` is
tightened/expanded to treat `0.0.0.0/8` as private and avoid
misclassifying bare IPv4 decimals as IPv6.
>
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
8261da4ff0. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
- 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
fix for #17262
This change ensures that when the record is restored, instead of
emitting a database event of "DELETED", a database event of "RESTORED"
will be emitted, as it is happening in the inner working of TypeORM.
This ensures that the DELETED event are not fired on RESTORED, for
example, triggering a workflow.
The screen recording shows that restoring the soft deleted records does
not trigger the workflow set to to run on "Record Deleted"
https://github.com/user-attachments/assets/90e0184f-2e08-466c-a40d-1592b60e64ff
Fixes https://github.com/twentyhq/core-team-issues/issues/2192
This PR implements what is necessary to re-create the query that we
build on the frontend to obtain the returned object record from a
mutation, but on the backend, which was only partially implemented for
REST API.
Usually we want to have relations with only their id and label
identifier field to have lighter payloads.
In the event we only had depth 0 fields, with this PR we have all events
with depth 1 relations.
We have depth 2 for many-to-many cases, like updateOne or updateMany
result :
- Junction tables
- Activity target tables
## Context
- Add missing fields widget and FIELDS_WIDGET view for workflow run and
workflow version standard objects
- Fix FIELDS_WIDGET configuration fieldId universalIdentifier not being
converted to id when migration is executed.
- Replaced the `IconWithBackground` component with a new `NavigationMenuItemStyleIcon` component for better styling and color management.
- Updated various components to utilize the new icon component, enhancing consistency in icon rendering across the application.
- Removed unused imports and simplified icon color logic by integrating color styles directly into the new component.
- Deleted the `getIconBackgroundColorForPayload` utility as it is no longer needed with the new icon handling approach.
Closes#8305
The Clipboard API (`navigator.clipboard`) requires a secure context
(HTTPS or localhost). Self-hosted deployments on plain HTTP silently
fail when copying.
This PR:
- Adds a `document.execCommand('copy')` fallback for insecure contexts
- Shows a descriptive error message explaining HTTPS is required when
the fallback also fails
- Consolidates 3 components that were using `navigator.clipboard`
directly (without error handling) to use the centralized
`useCopyToClipboard` hook
Generated with [Claude Code](https://claude.ai/code)
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> **Low Risk**
> Scoped to frontend clipboard UX with a defensive fallback and clearer
errors; minimal impact outside copy flows.
>
> **Overview**
> Improves copy-to-clipboard behavior in non-HTTPS/self-hosted
deployments by enhancing `useCopyToClipboard` to use
`navigator.clipboard` only in secure contexts and otherwise fall back to
`document.execCommand('copy')`.
>
> Updates 2FA setup screens and the view visibility dropdown to use the
centralized `copyToClipboard` helper (with consistent snackbars), and
shows a more descriptive error (longer duration) when copying fails due
to an insecure context.
>
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
30944e63eb. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
---------
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Charles Bochet <charlesBochet@users.noreply.github.com>
## Summary
Replaces the static "Ask AI" header in the command menu with the
conversation’s auto-generated title once it’s set after the first
message.
## Changes
- **Backend:** Title is generated after the first user message (existing
behavior).
- **Frontend:** After the first stream completes, we fetch the thread
title and sync it to:
- `currentAIChatThreadTitleState` (persists across command menu
close/reopen)
- Command menu page info and navigation stack (so the title survives
back navigation)
- **Entry points:** Opening Ask AI from the left nav or command center
uses the same title resolution (explicit `pageTitle` → current thread
title → "Ask AI" fallback).
- **Race fix:** Title sync only runs when the thread that finished
streaming is still the active thread, so switching threads mid-stream
doesn’t overwrite the current thread’s title.
---------
Co-authored-by: Félix Malfait <felix@twenty.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
## Summary
- Fixes "Target field metadata full object not found" error thrown
during optimistic effects (e.g., bulk delete) on workspaces with custom
objects
- The relation loader was using a simple sort-by-ID to pick the
representative morph field, while `filterMorphRelationDuplicateFields`
uses `pickMorphGroupSurvivor` which prefers active non-system fields.
When a custom object's auto-created morph field (`isSystem: true`)
happened to have the smallest UUID, the two loaders would disagree — the
relation DTO pointed to that system field's ID, but the field metadata
loader filtered it out in favor of a standard field, causing the
frontend lookup to fail.
- Now both code paths use `pickMorphGroupSurvivor` so they always agree
on which morph field represents the group.
## Test plan
- [ ] Create a custom object on a workspace that already has standard
objects with morph relations (e.g., noteTarget, taskTarget)
- [ ] Bulk-select and delete records (e.g., People) — should no longer
throw "Target field metadata full object not found"
Made with [Cursor](https://cursor.com)
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> **Low Risk**
> Small, localized change to morph-relation selection logic in a
dataloader; main risk is altered field choice for edge-case morph
groups, but behavior now matches existing deduplication.
>
> **Overview**
> Ensures the relation dataloader picks the representative
morph-relation target field using `pickMorphGroupSurvivor` (preferring
active non-system fields) instead of the previous sort-by-id approach.
>
> This aligns `createRelationLoader` with
`filterMorphRelationDuplicateFields`, preventing mismatches where
relation DTOs could reference a morph field that gets filtered out
elsewhere (e.g., triggering “Target field metadata full object not
found”).
>
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
c3a6d86126. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
Co-authored-by: Cursor <cursoragent@cursor.com>
## Summary
- update AI chat message typography and list line-height for readability
- apply richer markdown-section styling for headings, spacing,
separators, and inline code
- keep links non-underlined by default with underline on hover, using
accent11 for link color
- preserve previous AI chat table design while keeping other markdown
improvements
## Validation
- yarn eslint
packages/twenty-front/src/modules/ai/components/LazyMarkdownRenderer.tsx
packages/twenty-front/src/modules/ai/components/AIChatMessage.tsx
---------
Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
When deploying with
```
IS_WORKSPACE_CREATION_LIMITED_TO_SERVER_ADMINS=true
IS_MULTIWORKSPACE_ENABLED=true
```
The first workspace can be created successfully. However, any attempt to
create additional workspaces as admin fails with the error: `Workspace
creation is restricted to admins` because `canAccessFullAdminPanel` is
**false**
If these flags are set to false during the initial deployment and
restarting the Docker container, workspace creation works normally.
Problem is caused by `canAccessFullAdminPanel`
---------
Co-authored-by: ehconitin <nitinkoche03@gmail.com>
Co-authored-by: Félix Malfait <felix@twenty.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
- Removed the styled component for color label and replaced it with a direct display of the color label in the dropdown.
- Simplified the rendering logic for the color option in the command menu, enhancing readability and maintainability.
# 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
## Summary
- Add default visible view fields for `timelineActivity`, `attachment`,
`noteTarget`, `taskTarget`, and `workspaceMember` objects so they
display useful columns out of the box
- Standardize morph relation field labels to "Target" with
`IconArrowUpRight` for consistency across all pivot/junction tables
- Mark deprecated fields (`fullPath`, `fileCategory`,
`linkedRecordCachedName`, `linkedRecordId`, `linkedObjectMetadataId`) as
`isSystem` to hide them from the UI column picker
- Fix morph field deduplication logic (`pickMorphGroupSurvivor`) to
prefer active, non-system fields over auto-generated system fields from
custom objects
- Migrate attachment seeds from legacy `fullPath`/`fileCategory` to the
new `FILES` field type, creating proper `FileEntity` records in
`core.file` via `fileStorageService.writeFile()`
- Restore `customDomain` in the user query fragment
<img width="825" height="754" alt="Screenshot 2026-02-15 at 15 44 27"
src="https://github.com/user-attachments/assets/9596a3dd-8d3a-43c0-925a-0adef9ee68a8"
/>
<img width="736" height="731" alt="Screenshot 2026-02-15 at 15 44 13"
src="https://github.com/user-attachments/assets/cd1a66c5-731d-43e6-bbc3-703cbeda1652"
/>
<img width="722" height="757" alt="Screenshot 2026-02-15 at 15 44 03"
src="https://github.com/user-attachments/assets/b5210546-6a40-4940-8e4f-874818a614fb"
/>
<img width="907" height="757" alt="Screenshot 2026-02-15 at 15 43 52"
src="https://github.com/user-attachments/assets/ead5b9a8-1989-4d68-9640-583da6233711"
/>
<img width="1002" height="731" alt="Screenshot 2026-02-15 at 15 43 38"
src="https://github.com/user-attachments/assets/38accb8c-f5d5-4bfc-b245-06389849810b"
/>
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> **Medium Risk**
> Touches migration/upgrade commands that write to core metadata tables
and adjust field/view definitions, plus changes dev seeding to create
`core.file` records; mistakes could affect UI visibility or seed
integrity across workspaces.
>
> **Overview**
> Adds a new `upgrade:1-18:backfill-standard-views-and-field-metadata`
command that, per workspace, marks specific fields as `isSystem`,
normalizes morph-relation field `label`/`icon` to
`Target`/`IconArrowUpRight`, and backfills missing standard
`view`/`viewField` rows for `attachment`, `noteTarget`, `taskTarget`,
`timelineActivity`, and `workspaceMember`, followed by cache
invalidation + metadata version bump.
>
> Refactors morph-relation deduplication to pick a single survivor per
`morphId` using a new `pickMorphGroupSurvivor` rule (prefer active +
non-system, then smallest id), with new unit tests.
>
> Updates standard metadata generators and snapshots to reflect the new
system flags and default view fields, and rewrites attachment dev
seeding to populate the new `file` (FILES field) JSON and create
corresponding `core.file` entries via `FileStorageService.writeFile`
with workspace-scoped file IDs.
>
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
b1939bbf6f. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
- Introduced a new `CommandMenuEditColorOption` component to allow users to select and edit the color of navigation menu items.
- Integrated the color option into existing components: `CommandMenuEditLinkItemView`, `CommandMenuEditObjectViewBase`, and `CommandMenuNavigationMenuItemEditPage`.
- Updated the `useSaveNavigationMenuItemsDraft` and `useUpdateNavigationMenuItemInDraft` hooks to handle color changes in navigation menu items.
- Enhanced the user interface by providing a dropdown for color selection, improving customization options for navigation menu items.
- Changed default colors for `allCompanies`, `allPeople`, `allTasks`, and `allNotes` from indigo and teal to blue and turquoise, respectively.
- This update enhances visual consistency and improves the user interface of the navigation menu.
- Introduced an optional `color` field in the `CreateNavigationMenuItemInput`, `UpdateNavigationMenuItemInput`, and `NavigationMenuItem` types.
- Updated related GraphQL fragments to include the new `color` field for consistency across queries and mutations.
- Ensured that the `color` property is reflected in the `CreateNavigationMenuItemMutation` and `DeleteNavigationMenuItemMutation` responses.
- This change enhances the customization options for navigation menu items.
## Summary
- The `customDomain` field was accidentally removed from the
`currentWorkspace` GraphQL query fragment in #16016 (Nov 2025), when
`workspaceCustomApplication { id }` was added in its place rather than
alongside it.
- This caused the custom domain settings page to never display the
configured domain value, the reload/delete buttons, or the DNS records —
since `currentWorkspace.customDomain` was always `undefined`.
- Restores the missing field in the query fragment.
## Test plan
- [ ] Navigate to Settings > Domains on a workspace with a custom domain
configured
- [ ] Verify the custom domain value appears in the input field
- [ ] Verify the Reload and Delete buttons are visible
- [ ] Verify DNS records are displayed
Made with [Cursor](https://cursor.com)
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
- Introduced a new `color` property in the `NavigationMenuItem` entity and updated the corresponding DTOs and input types to support it.
- Modified migration to add the `color` column to the `navigationMenuItem` table.
- Updated various utility functions and constants to handle the new `color` property.
- Enhanced the `createStandardNavigationMenuItemFlatMetadata` function to assign default colors based on item type.
- Adjusted tests and snapshots to reflect the inclusion of the `color` property.
## Summary
- Add `@mention` support to the AI Chat text input by replacing the
plain textarea with a minimal Tiptap editor and building a shared
`mention` module with reusable Tiptap extensions (`MentionTag`,
`MentionSuggestion`), search hook (`useMentionSearch`), and suggestion
menu — all shared with the existing BlockNote-based Notes mentions to
avoid code duplication
- Mentions are serialized as
`[[record:objectName:recordId:displayName]]` markdown (the format
already understood by the backend and rendered in chat messages), and
displayed using the existing `RecordLink` chip component for visual
consistency
- Fix images in chat messages overflowing their container by
constraining to `max-width: 100%`
- Fix web_search tool display showing literal `{query}` instead of the
actual query (ICU single-quote escaping issue in Lingui `t` tagged
templates)
## Test plan
- [ ] Open AI Chat, type `@` and verify the suggestion menu appears with
searchable records
- [ ] Select a mention from the dropdown (via click or keyboard
Enter/ArrowUp/Down) and verify the record chip renders inline
- [ ] Send a message containing a mention and verify it appears
correctly in the conversation as a clickable `RecordLink`
- [ ] Verify Enter sends the message when the suggestion menu is closed,
and selects a mention when the menu is open
- [ ] Verify images in AI chat responses are constrained to the
container width
- [ ] Verify the web_search tool step shows the actual search query
(e.g. "Searched the web for Salesforce") instead of `{query}`
- [ ] Verify Notes @mentions still work as before
Made with [Cursor](https://cursor.com)
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
- Introduced a new variable `isAddMenuItemButtonVisible` to streamline the visibility logic for the add menu item button based on edit mode and dragging state.
- Updated the rendering condition for the add menu item button to use the new variable for improved clarity and maintainability.
- Introduced `StyledWorkspaceDroppableList` to replace a div for better styling in the Navigation Drawer.
- Updated `StyledFolderDroppableContent` to include flex properties and gap for consistent spacing.
Gmail 429/403 rate-limit responses include an explicit retry-after
timestamp, usually ~15 minutes out.
The exponential backoff starts at 1 minute, so the channel burns through
all 5 retry attempts before the window actually closes and gets marked
as permanently failed.
Adds throttleRetryAfter to the message channel and uses max(backoff,
retryAfter) in isThrottled().
## Context
Creating a new object should now also create its record page layout,
tabs and widgets, including fields widget with its associated views/view
fields.
Custom objects record page layout fields widgets don't have section per
default
Note: I had to enable some widget creation through the custom API but we
should now implement proper validation (which should be minimal since
there is usually only the configuration type in the configuration
(except for FIELDS widget which contains a viewId)
Next step: Create view field should also create a viewField for the
FIELDS_WIDGET view (we should also add in the FIELDS widget
configuration a newFieldDefaults which will contain default visibility
and position to apply to the new view field)
The feature is still gated behind an env variable (this was necessary
for workspace creation, not so much here in this case but I prefer to
keep the same path for consistence)
This PR addresses TODO comments and improves code quality:
### 1. PullRequestItem.tsx
- Replaced `react-tooltip` with `twenty-ui` `AppTooltip` component
- Removed TODO comment
- Uses internal component library for consistency
### 2. MenuItemAvatar.tsx
- Refactored to use `MenuItem` internally, eliminating code duplication
- Removed about 63 lines of duplicate code
- Removed TODO comment as the merge is now complete
---------
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
- moves workspace:* dependencies to dev-dependencies to avoid spreading
them in npm releases
- remove fix on rollup.external
- remove prepublishOnly and postpublish scripts
- set bundle packages to private
- add release-dump-version that update package.json version before
releasing to npm
- add release-verify-build that check no externalized twenty package
exists in `dist` before releasing to npm
- works with new release github action here ->
https://github.com/twentyhq/twenty-infra/pull/397
## Split twenty-sdk build into separate Node and browser targets
The SDK was bundling Node.js code (CLI, SDK API) and browser code (UI
components, front-component renderer) through a single Vite config. This
caused incorrect externalization — Node builtins leaked into browser
bundles and browser-specific chunking logic applied to CLI output.
This PR splits the build into `vite.config.node.ts` and
`vite.config.browser.ts` so each target gets the right externals and
output format.
Also includes a few housekeeping renames:
- `front-component` export path → `front-component-renderer` (matches
what it actually is)
- `front-component-common` merged into `front-component-api` (was a
needless extra module)
#17147 removed the root ./nx script, but wrapper-mode artifacts were
still present (installation in
[nx.json](https://github.com/twentyhq/twenty/blob/main/nx.json) and
tracked
[nxw.js](https://github.com/twentyhq/twenty/blob/main/.nx/nxw.js),
leaving an inconsistent setup.
This PR completes that cleanup by:
- removing installation from nx.json
- deleting tracked nxw.js
- ignoring nxw.js to prevent accidental re-introduction
Validated that Nx still works via yarn nx / npx nx.
Fixes issue where tabs were synchronized when opening two records of the
same type in show page and side panel.
The root cause was that tab instance IDs were only based on
`pageLayoutId`, causing all records using the same page layout to share
the same tab state.
This change includes the record ID in the tab instance ID, making tabs
unique per record while maintaining backward compatibility for cases
where no record ID is available.
Fixes#17522
---------
Co-authored-by: Eruis <github@eruis.example>
# Introduction
Splitting the create syncable entity rule into dedicated scoped skills
in order to favorise multi agent pattern with more granular context
### Multi-Agent Workflow
For parallel development:
1. **Agent 1** (Foundation): Complete Step 1 first - unblocks everyone
2. **Agent 2** (Cache): Can start immediately after Step 1
3. **Agent 3** (Builder): Can work in parallel with Agent 4 after Step 1
4. **Agent 4** (Runner): Can work in parallel with Agent 3 after Step 1
5. **Agent 5** (Integration): Assembles everything after Steps 2-4
## Summary
- Removed `vite-plugin-dts` (which used `tsc` internally) from the Vite
build and replaced DTS generation with `tsgo` as a sequential post-build
step — **~0.7s vs 1-10s**.
- Disabled `reportCompressedSize` to skip gzip computation for 64 output
files.
- Converted the build target to an explicit `nx:run-commands` executor
with sequential `vite build` → `tsgo` commands.
The `twenty-emails:build` step goes from ~22s to ~7s under load.
## Test plan
- [x] `nx build twenty-emails` produces both JS (64 files) and DTS (74
files) correctly
- [x] `dist/index.d.ts` exports match the source `src/index.ts`
- [x] Full `nx build twenty-server` succeeds end-to-end
- [ ] CI build passes
Made with [Cursor](https://cursor.com)
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
# Introduction
Avoid sending flat entity that contains server specific properties such
as foreignKey aggregators and universal properties by transpiling from
flat entity to scalar flat entity
A scalar flat entity is the exact match with the entities columns
Following https://github.com/twentyhq/twenty/pull/17622
## Summary
- **Fix token renewal endpoint**: Use `/metadata` instead of `/graphql`
for token renewal in agent chat, fixing auth issues
- **Improve tool display**: Add `load_skills` support, show formatted
tool names (underscores → spaces) with finish/loading states, display
tool icons during loading, and support custom loading messages from tool
input
- **Refactor workflow agent management**: Replace direct
`AgentRepository` access with `AgentService` for create/delete/find
operations in workflow steps, improving encapsulation and consistency
- **Simplify Apollo client usage**: Remove explicit Apollo client
override in `useGetToolIndex`, add `AgentChatProvider` to
`AppRouterProviders`
- **Fix load-skill tool**: Change parameter type from `string` to `json`
for proper schema parsing
- **Update agent-chat-streaming**: Use `AgentService` for agent
resolution and tool registration instead of direct repository queries
## Test plan
- [ ] Verify AI agent chat works end-to-end (send message, receive
response)
- [ ] Verify tool steps display correctly with icons and proper messages
during loading and after completion
- [ ] Verify workflow AI agent step creation and deletion works
correctly
- [ ] Verify workflow version cloning preserves agent configuration
- [ ] Verify token renewal works when tokens expire during agent chat
Made with [Cursor](https://cursor.com)
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
This PR aims to fix: #17408
Main modification includes adding a new column for dropdown in the
object permission rule table (containing options for editing and
removal). Removal logic is implemented using existing pattern with hook
```useResetObjectPermission``` (relies on existing hooks
```useUpsertFieldPermissionInDraftRole``` and
```useUpsertObjectPermissionInDraftRole```).
Feel free to suggest any necessary changes. Functionality (unrestricted
access is allowed when permission removal is applied) is already tested.
Demo video:
https://drive.google.com/file/d/1M4RYHw-JEhDdJksKkL3MY_VyXAV3aS9I/view?usp=sharing
---------
Co-authored-by: Etienne <45695613+etiennejouan@users.noreply.github.com>
As attachment files have migrated from fullPath to file files field,
need to migrate richText logic to fit to new attachment file handling +
data migration
In this PR
- Content tab for marketplace apps and installed apps: complies with
[figma](https://www.figma.com/design/xt8O9mFeLl46C5InWwoMrN/Twenty?node-id=34845-126856);
addition of field section showing fields added to standard objects;
initiative: arrow opens a sub-table of fields rows. (suggested because
1/ for marketplace apps we cannot redirect to the actual object page in
settings since the object does not exist yet in the workspace 2/ since
we dont have an quick "go back to application page" option, it can be
annoying to be redirected to a different setting page when we just want
to look at the content of the app objects)
- Permission tab for marketplace apps and installed apps
- left to do - settings tab (in another PR)
There are breaking changes but it's behind a feature flag not exposed
(access to marketplace) so not problematic
marketplace apps
https://github.com/user-attachments/assets/4c660101-50fc-47ce-b90a-8d6f17db5e74
installed apps
https://github.com/user-attachments/assets/c9229ee1-e75f-4cad-8766-758b2c5b37b4
## Summary
This PR adds **metadata eventing**: when schema metadata
(objectMetadata, fieldMetadata, view, viewField, etc.) is created,
updated, or deleted, we now emit events that can trigger webhooks and
future audit logs. It also adds **actor context** (`userId`,
`workspaceMemberId`) to those events so subscribers can attribute
changes to a user or API key.
## What changed
### 1. Metadata eventing (first commit)
- **MetadataEventEmitter**
New service that emits batch events after successful workspace
migrations. Event names follow `metadata.{entity}.{action}` (e.g.
`metadata.objectMetadata.created`, `metadata.fieldMetadata.updated`).
- **MetadataEventsToDbListener**
Listens for metadata events and enqueues webhook delivery via
`CallWebhookJobsForMetadataJob`.
- **Event types** (twenty-shared)
`MetadataEventAction`, `MetadataEventBatch`, and record event types for
create/update/delete.
- **WorkspaceMigrationValidateBuildAndRunService**
Calls the metadata event emitter after running migrations so all
metadata changes (from any module) emit events from a single place.
- **Create events**
Sourced from the create action payload (`flatEntity` /
`flatFieldMetadatas`) because `fromToAllFlatEntityMaps` does not provide
a before/after diff for creates. Update/delete events still use the
fromToAllFlatEntityMaps comparison.
### 2. Actor context (second commit)
- **MetadataEventEmitter**
Accepts optional `actorContext` (`userId`, `workspaceMemberId`) and
includes it on emitted batch events.
- **WorkspaceMigrationValidateBuildAndRunService**
Passes `actorContext` from the request into the metadata event emitter.
- **Metadata resolvers & services**
All metadata modules resolve `@AuthUser({ allowUndefined: true })` and
`@AuthUserWorkspaceId()` and pass `userId` and `workspaceMemberId`
through to the migration/event pipeline. Both are optional so
API-key–authenticated requests (no user) still emit events without a
user identity.
Shared some questions on Discord about the PR.
---------
Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
Co-authored-by: prastoin <paul@twenty.com>
# Introduction
## Use `flatEntityTranspilers.toScalarFlatEntity` in create action
handler
**Changes:**
- Modified
`BaseWorkspaceMigrationRunnerActionHandlerService.insertFlatEntitiesInRepository()`
to transform flat entities using `toScalarFlatEntity()` before database
insertion
**What it does:**
Strips out TypeORM relation objects and metadata-only properties,
ensuring only scalar values (primitives, IDs, dates) are inserted into
the database.
**Benefits:**
- **Type Safety:** Prevents accidental insertion of nested objects that
TypeORM can't persist
- **Consistency:** All 17+ create action handlers automatically benefit
from proper data transformation
- **Single Source of Truth:** Centralized logic for what constitutes a
database-insertable entity
- **Prevents Errors:** Uses entity configuration schema to ensure only
valid properties are included
## Usage
```ts
protected async insertFlatEntitiesInRepository({
flatEntities,
queryRunner,
}: {
queryRunner: QueryRunner;
flatEntities: MetadataFlatEntity<TMetadataName>[];
}) {
const metadataEntity =
ALL_METADATA_ENTITY_BY_METADATA_NAME[this.metadataName];
const repository = queryRunner.manager.getRepository(metadataEntity);
const scalarFlatEntities = flatEntities.map((flatEntity) =>
flatEntityTranspilers.toScalarFlatEntity({
flatEntity,
metadataName: this.metadataName,
}),
);
await repository.insert(scalarFlatEntities);
}
```
## Upcoming refactor
About to completely split the
`packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-runner/interfaces/workspace-migration-runner-action-handler-service.interface.ts`
into three dedicated boilerplate one for each action type `create`
`delete` `update` will provide a better interfacing and typing + will
allow not requiring the user to provide the metadata execute handler as
required
## Context
Prefill the FIELDS widget configuration in standard page layouts during
workspace creation, linking each widget to a dedicated view with
positioned fields organized into sections (via view field groups)
We wanted something very declarative (by manually setting position and
visibility of each field per standard object).
In this PR I've generated all the compute- utils via AI (😨) for
position/visibility, we'll probably want to confirm with the product
which ordering/visibility we want for each standard object but I feel
like this can be merged as it is since it's behind a feature flag and
this will unblock the work on the frontend
## Summary
Fixes `EMFILE: too many open files, watch` crash that most of the team
is hitting on macOS when running `yarn start` or `npx nx start
twenty-server`.
Adds `rimraf dist` before `nest start --watch` in the `start` and
`start:debug` targets, so the watcher starts with a clean output
directory.
## Root cause
The NestJS SWC compiler (`@nestjs/cli@11`) creates **three overlapping
chokidar watchers** when `nest start --watch` runs:
| Watcher | Watches | Purpose | Handles |
|---|---|---|---|
| SWC CLI (`@swc/cli`) | `src/` | Detects file changes → recompiles |
~1,730 |
| NestJS `watchFilesInSrcDir` | `src/` | Workaround: SWC misses new
files | shared with above |
| NestJS `watchFilesInOutDir` | **`dist/`** | Detects compiled `.js` →
restarts server | **~3,548** |
`@nestjs/cli@11` ships with **chokidar v4**, which dropped macOS
`fsevents` support and uses `fs.watch()` instead — creating **one file
descriptor per directory**. Chokidar v3 used a single `fsevents` kernel
subscription per directory tree.
Total: **~5,000+ `fs.watch()` handles**, far exceeding the default macOS
`ulimit -n` of ~2,560.
### Why it broke now
PR #17851 (`15fc850212`) changed the `start` target from `dependsOn:
["build"]` to `dependsOn: ["^build"]`, removing the `rimraf dist && nest
build` pre-step. Without that cleanup, `dist/` accumulated stale
directories from code reorganizations (e.g. `application-layer/` →
`application/` rename), growing to ~3,548 directories vs ~1,730 in a
clean build.
## What this PR does
Adds `rimraf dist &&` before `nest start --watch` in the `start` and
`start:debug` commands. This ensures `dist/` starts empty and only
contains directories matching the current `src/` structure (~1,730),
keeping watcher count in the ~3,400 range.
We still get the startup speed improvement from #17851 (no redundant
full SWC build), since `rimraf dist` is ~instant while the removed `nest
build` step took 30-60s.
## Future considerations
As the codebase grows, even a clean `dist/` will eventually approach the
macOS default `ulimit -n` (~2,560). Options to consider if that happens:
1. **Yarn resolution to force chokidar 3.6.0** — restores `fsevents`,
reducing watcher count from ~5,000 to ~3-5. This is what Vite 7 does
internally. Simple and effective, but pins to an older major version.
2. **Patch `@nestjs/cli`** to skip the `dist/` watcher — the
`watchFilesInOutDir` watcher accounts for ~65% of all handles and only
exists because NestJS doesn't have a direct hook into SWC's
compilation-complete event. Could be removed via `yarn patch`.
3. **Replace `nest start --watch` entirely** — use `node
--watch-path=src` (Node 22+) with `@swc-node/register` for on-the-fly
compilation. Uses a single native watcher regardless of directory count.
Requires rethinking asset copying (`watchAssets` in `nest-cli.json`).
4. **Wait for upstream fix** — NestJS CLI should either re-add
`fsevents` support or use Node's recursive `fs.watch()` option
(available since Node 20) instead of per-directory watchers.
## Test plan
- [ ] Run `npx nx start twenty-server` on macOS — server starts without
EMFILE error
- [ ] Run `npx nx start:debug twenty-server` — debug mode starts without
EMFILE error
- [ ] Edit a `.ts` file while server is running — hot reload still works
- [ ] Run `yarn start` (frontend + backend + worker) — no crashes
Made with [Cursor](https://cursor.com)
Co-authored-by: Cursor <cursoragent@cursor.com>
## Remove Recoil from twenty-ui
Completely removes the `recoil` dependency from `twenty-ui` by
converting all atoms, hooks, and providers to Jotai equivalents.
### twenty-ui
- `createState` now returns a Jotai `PrimitiveAtom` instead of a Recoil
atom
- `iconsState`, `IconsProvider`, `useIcons` converted to Jotai
(`useSetAtom`, `useAtomValue`)
- `RecoilRootDecorator` now uses Jotai `Provider` (name kept for compat)
- Deleted unused `invalidAvatarUrlsState` (Avatar already uses
`invalidAvatarUrlsAtomV2`)
- Removed `recoil` from `package.json`
### twenty-front
- Created local Recoil `createState` at
`@/ui/utilities/state/utils/createState` for ~112 state files still on
Recoil
- Updated all imports accordingly
- Removed `iconsState` from Recoil snapshot preservation in `useAuth`
(lives in Jotai store now)
# Introduction
Related https://github.com/twentyhq/core-team-issues/issues/2227
On a field name update side effect leading to an index field mutation it
wouldn't get caught by the builder leading to an index field desync
We should land on a standard pattern regarding the field index either
jsonb or syncableEntity so this would not occur anymore as it would have
been strictly typed
## Recoil → Jotai progressive migration: infrastructure +
ChipFieldDisplay
### Benchmark
In the beginning, there was no hope:
<img width="1180" height="948" alt="image"
src="https://github.com/user-attachments/assets/f8635991-52e6-4958-8240-6ba7214132b2"
/>
Then the hope was reborn
<img width="2070" height="948" alt="image"
src="https://github.com/user-attachments/assets/be1182b9-1c8d-4fdc-ab4c-1484ad74449d"
/>
### Approach
We introduce a **V2 state management layer** backed by Jotai that
mirrors the existing Recoil API, enabling component-by-component
migration without a big-bang rewrite.
#### V2 API (Jotai-backed, Recoil-ergonomic)
- `createStateV2` / `createFamilyStateV2` — drop-in replacements for
`createState` / `createFamilyState`, returning wrapper types over Jotai
atoms
- `useRecoilValueV2`, `useRecoilStateV2`, `useFamilyRecoilValueV2`, etc.
— thin wrappers around Jotai's `useAtomValue` / `useAtom` / `useSetAtom`
- A shared `jotaiStore` (via `createStore()`) passed to a
`<JotaiProvider>` wrapping `<RecoilRoot>`, also accessible imperatively
for dual-writes
#### Dual-write bridge for progressive migration
For state shared between migrated and non-migrated components, we use
**dual-write**: writers update both the Recoil atom and the Jotai V2
atom (via `jotaiStore.set()`). This avoids sync components or extra
subscriptions.
Write sites updated: `useUpsertRecordsInStore`, `useSetRecordTableData`,
`ListenRecordUpdatesEffect`, `RecordShowEffect`,
`useLoadRecordIndexStates`, `useUpdateObjectViewOptions`.
#### First migration: ChipFieldDisplay render path
- `useChipFieldDisplay` → reads `recordStoreFamilyStateV2` via
`useFamilyRecoilValueV2` (was `useRecoilValue(recordStoreFamilyState)`)
- `RecordChip` → reads `recordIndexOpenRecordInStateV2` via
`useRecoilValueV2` (was `useRecoilValue(recordIndexOpenRecordInState)`)
- `Avatar` (twenty-ui) and event handlers (`useOpenRecordInCommandMenu`)
left on Recoil — not on the render path / in a different package
#### Pattern for migrating additional state
1. Create V2 atom: `createStateV2` or `createFamilyStateV2`
2. Add `jotaiStore.set(v2Atom, value)` at each write site
3. Switch readers to `useRecoilValueV2(v2Atom)`
4. Once all readers are migrated, remove the Recoil atom and dual-writes
#### Why not jotai-recoil-adapter?
Evaluated
[jotai-recoil-adapter](https://github.com/clockelliptic/jotai-recoil-adapter)
— not production-ready (21 open issues, no React 19, forces providerless
mode, missing types). We built a purpose-built thin layer instead.
Bumps
[@types/bytes](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/bytes)
from 3.1.4 to 3.1.5.
<details>
<summary>Commits</summary>
<ul>
<li>See full diff in <a
href="https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/bytes">compare
view</a></li>
</ul>
</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>
## PR description
This PR:
- Creates a TypeScript-based extractor that discovers all exported
twenty-ui components by scanning barrel files, extracting
props/slots/events via ts-morph type analysis, and generating the remote
DOM bindings automatically.
- Adds a new ESLint rule which enforces all *Props types in twenty-ui
components to be exported, which is required for the extractor to
discover component prop types. Existing twenty-ui components are updated
to comply with this rule.
- Extends the remote DOM generation to support slots, per-component
events, forwardRef wrappers, and richer property types (array, object,
function)
## Edge cases to fix in another PR
- Icons cannot be rendered inside buttons
- IconButtons throw an error when mounted
- MenuItems are not displayed correctly
- MenuItemNavigate throws on click
## Video Demo
https://github.com/user-attachments/assets/c2ed67cf-6a15-4896-9fec-e83fac0e862b
## Reduce type leakage between GraphQL schemas
### Why
Twenty runs two separate GraphQL schemas: **core** and **metadata**.
NestJS's `@nestjs/graphql` uses a global `TypeMetadataStorage` that
accumulates all decorated types across all modules. When each schema is
built, every registered type leaks into both schemas regardless of which
module it belongs to.
This means the core schema's generated TypeScript
(`generated/graphql.ts`) contained ~2,700 lines of types that only
belong to the metadata schema (and vice versa). This creates confusion
about type ownership, inflates generated code, and makes it harder to
reason about which API surface each schema actually exposes.
### How
**1. Patch `@nestjs/graphql` to support schema-scoped type resolution**
- **(Already done)** Added a `resolverSchemaScope` option to
`GqlModuleOptions`, allowing each schema to declare a scope (e.g.
`'metadata'`)
- `ResolversExplorerService` now filters resolvers by a
`RESOLVER_SCHEMA_SCOPE` metadata key, so each schema only sees its own
resolvers
- `GraphQLSchemaFactory` now performs a **reachability walk**
(`computeReachableTypes`) starting from scoped resolver return types and
arguments, only including types that are transitively referenced —
handling unions, interfaces, and prototype chains
- Type definition storage and orphaned reference registry are cleared
between schema builds to prevent cross-contamination
**2. Register `ClientConfig` as orphaned type in metadata schema**
Since `ClientConfig` is needed in the metadata schema but not directly
returned by a resolver, it's explicitly declared via
`buildSchemaOptions.orphanedTypes`.
**3. Regenerate frontend types and fix imports**
- `generated/graphql.ts` shrank by ~2,700 lines (types moved to where
they belong)
- `generated-metadata/graphql.ts` gained types like `ClientConfig` that
were previously missing
- ~500 frontend files updated to import from the correct generated file
This update introduces an icon property for the workflowsFolder in the standard navigation menu items, enhancing the metadata structure. The icon is also integrated into the relevant utility functions to ensure consistency across the navigation menu item representations.
This update introduces a new prop, alignWithCommandMenuTopBar, to the StyledButtonWrapper, allowing the button to adjust its position based on the command menu's state and the navigation menu's edit mode. The changes improve the user interface for mobile users by ensuring better alignment and visibility of the command menu toggle button.
Addresses review comments from the [first navbar customization
PR](https://github.com/twentyhq/twenty/pull/17728)
---------
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
Co-authored-by: Devessier <baptiste@devessier.fr>
This change standardizes the import statement for the FOLDER_ICON_DEFAULT constant across multiple files, ensuring uniformity in naming conventions and improving code clarity.
This update modifies the PageChangeEffect component to utilize useRecoilCallback, allowing the command menu to close conditionally based on the current page state. The previous logic has been streamlined to enhance readability and maintainability.
This update reorganizes the import statements in the NavigationDrawerSectionForWorkspaceItems.tsx file for improved clarity and consistency. The changes include moving some imports to maintain a logical order and enhance readability.
This update introduces the commandMenuPage state to the PageChangeEffect component, preventing the command menu from closing when editing a navigation menu item. Additionally, the WorkspaceNavigationMenuItems component now utilizes the useNavigate hook for improved navigation handling, ensuring links are only followed if they are non-empty strings.
This update introduces a new feature flag, IS_NAVIGATION_MENU_ITEM_EDITING_ENABLED, to the seedFeatureFlags utility, enabling the editing of navigation menu items within the workspace.
## Context
Introduces a new viewFieldGroup entity that allows grouping view fields
into sections (e.g. "General", "Additional", "Other") within a view.
The page layout fields widget needs a way to organize fields into
sections. Today, views have no concept of field grouping. This PR
introduces the viewFieldGroup entity which sits between a view and its
viewFields, enabling section-based organization.
<img width="401" height="724" alt="Layout - V2 (customize visibility)"
src="https://github.com/user-attachments/assets/6376e2ab-44db-42bf-9d2c-758f56f6b548"
/>
---------
Co-authored-by: Charles Bochet <charles@twenty.com>
## Add application-scoped GraphQL schema generation
When an application token is used to authenticate, the `/graphql` schema
is now dynamically filtered to only include entities belonging to that
application (plus the Twenty Standard Application). This enables
third-party applications and the SDK to introspect a schema that is
relevant to their scope, rather than seeing the full workspace schema
with all custom objects.
### Changes
- **New `generateApplicationToken` mutation** on the `/metadata`
endpoint, allowing callers to exchange an API key for an
application-scoped JWT token
- **Schema filtering by application** in `WorkspaceSchemaFactory` — when
`request.application` is present (from an application token), flat
entity maps are filtered by `[appId, standardAppId]` before schema
generation
- **Per-app caching** — both the Yoga in-memory cache and Redis cache
now include the `appId` in their keys to avoid serving wrong schemas
- **Consolidated `getSubFlatEntityMapsByApplicationIdsOrThrow`** —
unified the single-ID and multi-ID filtering utilities into one
- **Integration tests** covering token generation (admin + API key auth)
and schema introspection filtering (standard app token excludes custom
objects)
Schema generated on seeds with applicationToken (see that pets is
missing)
<img width="782" height="994" alt="image"
src="https://github.com/user-attachments/assets/82510031-0965-435d-bc26-77c9f5d74e1f"
/>
## Summary
- Changed 6 Nx targets (`start`, `start:debug`, `typeorm`, `ts-node`,
`database:migrate`, `database:migrate:revert`) from `dependsOn:
["build"]` to `dependsOn: ["^build"]` to eliminate a redundant full SWC
compilation step (~30-60s per invocation).
- These targets all use `nest start --watch`, `ts-node`, or TypeORM's
CLI (which runs under ts-node), so they already compile TypeScript
themselves. The `build` dependency was causing `rimraf dist && nest
build` to run first, only for the target's own command to recompile
everything again.
- Fixed `start:debug` to call `nest start --watch --debug` directly
instead of routing through `nx start --debug`, which would trigger yet
another build cycle.
Note: targets that run pre-compiled code from `dist/` (like
`database:reset`) intentionally keep `dependsOn: ["build"]`.
## Test plan
- [ ] Run `npx nx start twenty-server` and verify the server starts with
only one SWC compilation pass instead of two
- [ ] Run `npx nx start:debug twenty-server` and verify the debugger
attaches correctly
- [ ] Run `npx nx run twenty-server:database:migrate` and verify
migrations run correctly
- [ ] Run `npx nx run twenty-server:database:reset twenty-server` and
verify it still builds before running (unchanged)
Made with [Cursor](https://cursor.com)
Co-authored-by: Cursor <cursoragent@cursor.com>
## Summary
- After deleting a workspace, the app was incorrectly redirecting to the
deleted workspace's subdomain (e.g. `myworkspace.ourapp.com/sign-in-up`)
because `signOut()` only performs a client-side React Router navigation
which stays on the current domain.
- Added an explicit `redirectToDefaultDomain()` call after sign out in
the workspace deletion flow, which does a hard browser redirect to the
base domain (e.g. `app.ourapp.com`).
## Test plan
- [ ] Delete a workspace in a multi-workspace environment
- [ ] Verify the browser redirects to the base domain (`app.ourapp.com`)
instead of staying on the deleted workspace's subdomain
- [ ] Verify normal sign-out (without workspace deletion) still works as
expected
Made with [Cursor](https://cursor.com)
Co-authored-by: Cursor <cursoragent@cursor.com>
## Summary
- The dash separator and blinking caret in the sign-in 2FA OTP input had
hardcoded `black` and `white` background colors, making them invisible
in dark mode (black on black / white on white).
- Replaced with theme-aware colors (`theme.font.color.light` for the
dash, `theme.font.color.primary` for the caret) to match the existing
settings 2FA component.
## Test plan
- [ ] Open the 2FA verification screen in dark mode and verify the dash
between digit groups is visible
- [ ] Verify the blinking caret is visible in dark mode
- [ ] Confirm both still look correct in light mode
Made with [Cursor](https://cursor.com)
Co-authored-by: Cursor <cursoragent@cursor.com>
## Context
Fix broken record page layout seeding. This was not detected by the CI
because it doesn't have the env variable yet.
Following the same mechanism as labelIdentifier in object for circular
dependency resolution
Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
Co-authored-by: Devessier <baptiste@devessier.fr>
- AI still often forgets to update the code step after creating it.
Adding a next step
- Starting by loading logic functions, so it avoids creating code steps
when a function exists
- Fix create complete workflow logic. Should not create code steps
directly
- Migration command
- Check IS_FILES_FIELD_MIGRATED:false
- Check or create avatarFile field
- Fetch all people with avatarUrl
- Move (Copy/move) file in storage
- Create core.file record
- Update person record
- bonus : attachment migration : fullPath > file (same logic)
- BE logic
- Add avatarFile field on person
- FE logic
- Adapt logic to upload on/display avatarFile data
The whole imageIdentifier logic will be done later
- Updated import statements in `CurrentWorkspaceMemberOrphanNavigationMenuItems` for improved organization.
- Reformatted conditional checks in `WorkspaceNavigationMenuItemsFolder` for enhanced readability.
- Adjusted import statements in `CurrentWorkspaceMemberNavigationMenuItems` and `CurrentWorkspaceMemberOrphanNavigationMenuItems` for consistency.
- Reformatted conditional checks in `CurrentWorkspaceMemberNavigationMenuItems` and `NavigationDrawerItemForObjectMetadataItem` for better clarity.
- Enhanced readability of the `isRecord` and `isView` variable assignments in `NavigationMenuItemIcon` and `NavigationDrawerItemForObjectMetadataItem` components.
- Split the combined hook `useAddFolderAndLinkToNavigationMenu` into two separate hooks: `useAddFolderToNavigationMenu` and `useAddLinkToNavigationMenu`.
- Updated `CommandMenuNewSidebarItemMainMenu` to utilize the new hooks for better modularity and clarity in handling folder and link additions to the navigation menu.
- Added command menu state management to NavigationMenuEditModeBar for improved navigation item handling.
- Implemented logic to close the command menu when specific navigation item pages are active, enhancing user experience during editing.
- Refactored component to utilize new command menu state, ensuring better synchronization between navigation and command menu actions.
- Added addToNavFallbackDestination to NavigationDropTargetContext for improved drag-and-drop functionality.
- Updated NavigationDrawerSectionForWorkspaceItems to utilize the new fallback destination logic, enhancing visibility and handling of orphan navigation items.
- Refactored conditional rendering to accommodate the new drop target behavior.
- Added support for fallback destinations when dragging items from the navigation menu.
- Introduced state management for orphan items and improved handling of drag events.
- Updated drag update logic to accommodate new fallback destination behavior.
- Refactored drag end logic to ensure proper handling of navigation drop actions.
- Added a new component, ObjectIconWithViewOverlay, to display icons with an overlay for navigation menu items.
- Updated CommandMenuNewSidebarItemViewPickerSubView to utilize the new overlay icon feature.
- Refactored NavigationMenuItemIcon to conditionally render the overlay based on item type.
- Modified NavigationDrawerItemForObjectMetadataItem to support the new icon rendering logic.
- Improved AddToNavigationDragHandle to accommodate custom icon content display.
- Introduced an optional `icon` field to the `NavigationMenuItem` type in GraphQL schemas.
- Updated related input types and fragments to include the new `icon` field.
- Refactored components to utilize the new icon feature, including `CommandMenuFolderInfo` and `WorkspaceNavigationMenuItems`.
- Replaced the previous folder name update hook with a more versatile `useUpdateFolderInDraft` hook to handle both name and icon updates.
- Added a default folder icon constant for better management of folder icons across the application.
## Summary
- When returning to the app after idle (or after a deploy), the expired
access token causes multiple simultaneous GraphQL queries to fail with
`UNAUTHENTICATED`. Previously, each failure independently triggered its
own `renewToken` call with the same refresh token. If **any single**
renewal failed (e.g. server briefly slow after a deploy), the `catch`
handler would nuke the session and redirect to sign-in — even if another
concurrent renewal had already succeeded and written valid tokens.
- This adds a shared `renewalPromise` so that only the first
`UNAUTHENTICATED` error triggers a server-side renewal. All concurrent
callers await the same promise and replay their operations once it
resolves. This eliminates redundant refresh token rotation on the server
and removes the race condition where a straggling failure could log out
an already-renewed session.
## Test plan
- [ ] Log in, wait >30 minutes (or manually expire the access token),
then interact with the app — should silently renew without redirect to
sign-in
- [ ] Open browser DevTools Network tab, trigger the above scenario, and
verify only **one** `renewToken` mutation is sent (instead of N)
- [ ] With server temporarily stopped, verify that a genuine renewal
failure still correctly redirects to sign-in (single
`onUnauthenticatedError` call)
- [ ] Open multiple browser tabs, let access tokens expire, interact in
one tab — other tabs should also recover gracefully on their next
request
Made with [Cursor](https://cursor.com)
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
- Added new scalar type `Upload` to support file uploads.
- Introduced multiple input types for creating and managing various entities, including `ActivateWorkspaceInput`, `CreateAgentInput`, and `CreateApiKeyInput`.
- Expanded the schema with new enums such as `AllMetadataName` and `AnalyticsType` for better categorization of metadata and analytics events.
- Added new mutation inputs for workflow management, including `CreateWorkflowVersionEdgeInput` and `DeleteWorkflowVersionStepInput`.
- Implemented additional filtering capabilities with `DateTimeFilter` to enhance query flexibility.
These changes improve the overall functionality and usability of the GraphQL API, enabling more robust interactions with the backend.
Refactored the logic for determining if the processed item is a view or record by using an array and the includes method for improved readability. Simplified the label assignment logic to enhance clarity. This change contributes to better maintainability and consistency in the CommandMenu components.
## Fix resolver schema leaking between `/metadata` and `/graphql`
endpoints
### Summary
- Patch `@nestjs/graphql` to support a `resolverSchemaScope` option that
filters resolvers at both schema generation and runtime, preventing
cross-endpoint leaking
- Introduce `@CoreResolver()` and `@MetadataResolver()` decorators to
explicitly scope each resolver to its endpoint
- Move most resolvers (auth, billing, workspace, user, etc.) to the
metadata schema where the frontend expects them; only workflow and
timeline calendar/messaging resolvers remain on `/graphql`
- Fix frontend `SSEQuerySubscribeEffect` to use the default (metadata)
Apollo client instead of the core client
### Problem
NestJS GraphQL's module-based resolver discovery traverses transitive
imports, causing resolvers from `/metadata` modules to leak into the
`/graphql` schema and vice versa. This made the schemas unpredictable
and tightly coupled to module import order.
### Approach
- Added `resolverSchemaScope` to `GqlModuleOptions` via a patch on
`@nestjs/graphql`, filtering in both `filterResolvers()` (runtime
binding) and `getAllCtors()` (schema generation)
- Each resolver is explicitly decorated with `@CoreResolver()` or
`@MetadataResolver()`
- Organized decorator, constant, and type files under `graphql-config/`
following project conventions
Core GQL Schema: (see: no more fields!)
<img width="827" height="894" alt="image"
src="https://github.com/user-attachments/assets/668f3f0f-485e-43f0-92be-4345aeccacb6"
/>
Metadata GQL Schema (see no more getTimelineCalendarEventsFromCompany)
<img width="827" height="894" alt="image"
src="https://github.com/user-attachments/assets/443913db-e5fe-4161-b0e7-4a971cc80a71"
/>
Introduced new components for managing sidebar item creation flows, including CommandMenuNewSidebarItemObjectFlow and CommandMenuNewSidebarItemViewFlow. Updated CommandMenuNewSidebarItemMainMenu to utilize the new hooks for adding folders and links, enhancing the overall structure and maintainability of the CommandMenu. This refactor improves user experience by streamlining the process of adding new items to the navigation menu.
Replaced string literals with the NavigationMenuItemType enum in the getObjectMetadataForNavigationMenuItem tests. This change enhances type safety and consistency across the test suite, aligning with recent updates in the codebase.
Introduced new components for managing sidebar item creation flows, including CommandMenuNewSidebarItemObjectFlow and CommandMenuNewSidebarItemViewFlow. Updated CommandMenuNewSidebarItemMainMenu to utilize the new hooks for adding folders and links, enhancing the overall structure and maintainability of the CommandMenu. This refactor improves user experience by streamlining the process of adding new items to the navigation menu.
## Why
When opening **Merge records** repeatedly, morph items for the same
command-menu page were appended instead of replaced. This could produce
duplicated IDs (e.g. `[A,B,B,A]`) in the merge flow and extra duplicate
tabs in the UI.
## What
- Update `useCommandMenuUpdateNavigationMorphItemsByPage` to replace
page morph items instead of appending existing ones.
- Add regression tests covering:
- replacing existing morph items for the same page
- keeping only the latest payload when called twice for the same page
## Notes
I could not run the full workspace tests locally in this environment
because of existing test/build setup issues unrelated to this change
(missing `packages/twenty-front/tsconfig.spec.json` and
`temporal-polyfill` resolution in dependent tasks).
Co-authored-by: remi <remi@labox-apps.com>
Replaced string literals with the NavigationMenuItemType enum in the getObjectMetadataForNavigationMenuItem tests. This change enhances type safety and consistency across the test suite, aligning with recent updates in the codebase.
# Introduction
Followup https://github.com/twentyhq/twenty/pull/17622
Refactoring the actions handler to be returning a metadata event
It has to be done incrementally, as if not update metadata event would
be stale as depends on the incremental action execution order and
optimistic application
## What's next
- Builder should consume and regroup each metadata even in order to
batch emit them ( within a single metadata actions batch order matters )
=> refactoring https://github.com/twentyhq/twenty/pull/17622 in order to
consume runner returned metadata events
## Summary
- `transformRichTextV2Value` was calling `await
import('@blocknote/server-util')` and `ServerBlockNoteEditor.create()`
on **every single invocation**, adding ~90ms of overhead each time
(visible as the highest avg-duration frame in profiling at 93.49ms).
- Cache the `ServerBlockNoteEditor` instance at module level so the
dynamic import + creation only happens once for the lifetime of the
process.
- Also removes debug timing instrumentation (`performance.now()`,
`calculateInputSize`, `Logger`) that is no longer needed.
## Test plan
- [ ] Verify rich text fields (blocknote/markdown) still round-trip
correctly on create and update
- [ ] Confirm reduced CPU time for `transformRichTextV2Value` in
profiling
Made with [Cursor](https://cursor.com)
Co-authored-by: Cursor <cursoragent@cursor.com>
Updated the normalizeUrl function to return an empty string for empty or whitespace-only input. This change improves the function's robustness and ensures it handles edge cases more gracefully. Additionally, variable names were clarified for better readability.
Updated the CommandMenuEditFolderPickerSubView and CommandMenuNavigationMenuItemEditPage components to streamline folder selection handling. Removed the useNavigationMenuItemEditSubView hook and replaced it with local state management for improved clarity and maintainability. The folder picker now directly manages its open/close state, enhancing the user experience and reducing complexity in the component structure.
Updated various CommandMenu components and utilities to replace string literals for item types with the NavigationMenuItemType enum. This change enhances type safety, consistency, and maintainability across the codebase, reducing the risk of errors related to item type handling.
Replaced string literals for item types in CommandMenuFolderInfo and CommandMenuLinkInfo components with the NavigationMenuItemType enum for improved type safety and consistency across the codebase. This change enhances maintainability and reduces the risk of errors related to item type handling.
Deleted outdated test files for getIconBackgroundColorForPayload, getNavigationMenuItemIconColors, and isWorkspaceDroppableId functions to streamline the test suite and eliminate redundancy. These tests are no longer necessary due to recent refactoring and updates in the utility functions.
- Enhanced the formatting of test cases in recordIdentifierToObjectRecordIdentifier, sortNavigationMenuItems, and validateAndExtractWorkspaceFolderId tests for better clarity.
- Added comments to the CommandMenuNewSidebarItemViewPickerSubView component to clarify prop spreading, improving maintainability and understanding of the code.
Refactored the CommandMenuNavigationMenuItemEditPage component to replace multiple if statements with a switch statement for improved readability and maintainability. This change enhances the handling of different navigation menu item types, ensuring clearer logic flow and reducing code duplication.
Updated the validateAndExtractWorkspaceFolderId utility to utilize the isNonEmptyString guard for improved validation of workspace folder IDs. This change enhances error handling by ensuring that only non-empty strings are accepted as valid folder IDs, contributing to better type safety and clarity in the navigation menu item logic.
Refactored navigation menu item components and hooks to replace the deprecated NAVIGATION_MENU_ITEM_TYPE constant with a new NavigationMenuItemType enum for improved type safety and clarity. Updated all relevant imports and usages across the codebase to ensure consistency and maintainability.
Refactored navigation menu item components and hooks to replace the NAVIGATION_MENU_ITEM_DROPPABLE_IDS constant with a new NavigationMenuItemDroppableIds enum for improved type safety and clarity. Updated all relevant imports and usages across the codebase to ensure consistency and maintainability.
Refactored the navigation menu item components to replace the existing NAVIGATION_SECTIONS constant with a new NavigationSections enum for improved type safety and clarity. Updated related components and utility functions to utilize the new enum, ensuring consistency across the codebase. Removed the deprecated NavigationSectionId type as part of this transition.
Refactored navigation menu item components and hooks to replace the old import paths for navigation sections with a new centralized import. Introduced two new utility functions, computeInsertIndexAndPosition and normalizeUrl, to enhance the management of navigation menu items and URL normalization. Added corresponding unit tests to ensure functionality and reliability of the new utilities.
Added a new enum, CommandMenuNavigationItemActions, to centralize action identifiers for the command menu navigation items. Updated CommandMenuEditOrganizeActions and getOrganizeActionsSelectableItemIds to utilize this enum, enhancing code consistency and maintainability across the command menu functionality.
Refactored multiple components and hooks to replace the deprecated useSelectedNavigationMenuItemEditData with more granular hooks: useSelectedNavigationMenuItemEditItem, useSelectedNavigationMenuItemEditItemLabel, and useSelectedNavigationMenuItemEditItemObjectMetadata. This change enhances code clarity and modularity, improving the management of selected navigation menu items across the command menu functionality.
Updated multiple components and hooks to utilize the new useDraftNavigationMenuItems hook, enhancing the management of draft navigation menu items. Removed the deprecated useNavigationMenuItemEditFolderData hook and adjusted related logic to ensure consistency across the command menu functionality.
Introduced two new components, CommandMenuFolderInfo and CommandMenuLinkInfo, to enhance the command menu functionality. These components allow users to edit folder names and link labels directly within the command menu, improving user experience and interaction. Updated CommandMenuPageInfo to integrate these new components based on the selected item type.
Adjusted the coverage thresholds in the Jest configuration to 48% for lines and 48% for functions, reflecting a revised standard for test coverage requirements.
Removed unnecessary checks for drop destination IDs in the useHandleAddToNavigationDrop hook. The logic now directly checks for defined folder IDs, streamlining the drop handling process and improving code readability.
Updated the coverage thresholds in the Jest configuration to 48.6% for lines and 48.4% for functions, reflecting a revised standard for test coverage requirements.
Adjusted the coverage thresholds in the Jest configuration to improve code quality metrics. The new thresholds are set to 49.5% for statements, 49.5% for lines, and 49.4% for functions, reflecting a more stringent requirement for test coverage.
Refactored existing test cases for navigation menu item utilities to improve clarity and consolidate similar assertions. Key changes include:
- Simplified test descriptions for better understanding.
- Combined multiple assertions into single tests where applicable.
- Enhanced coverage for edge cases, ensuring robust validation of utility functions.
These updates aim to maintain the reliability of navigation menu item utilities while improving the overall readability of the test suite.
Introduced a new test suite for the calculateNewPosition function, covering various scenarios including edge cases for moving items in a draggable list. The tests validate the correct position calculations when items are moved to the beginning, end, or within the list, ensuring reliable behavior of the drag-and-drop functionality.
- Updated the component to accept a new `customIconContent` prop for better icon customization.
- Refactored the payload registration logic to ensure it is registered on mouse events, improving drag-and-drop functionality.
- Cleaned up the code structure for better readability and maintainability.
- Introduced AddToNavigationDragHandleIcon to manage custom icon content and standard icons more effectively.
- Removed the AddToNavigationIconSlot component to streamline the codebase.
- Updated CommandMenuNewSidebarItemRecordItem and AddToNavigationDragHandle components to utilize the new icon handling approach.
Introduced new test files for various utility functions related to navigation menu items. The tests cover the following functionalities:
- `getDropTargetIdFromDestination`: Validates the correct drop target ID generation based on different droppable IDs.
- `getIconBackgroundColorForPayload`: Ensures the correct background color is returned for different payload types.
- `getNavigationMenuItemIconColors`: Confirms the correct theme colors are returned for various navigation menu item types.
- `isWorkspaceDroppableId`: Tests the identification of workspace droppable IDs under various conditions.
These tests enhance the reliability of the navigation menu item utilities by ensuring expected behaviors are maintained.
## Summary
- **`isValidCountryCode`** was using `Array.includes()` on ~250 country
codes (O(n) per call). Replaced with a `Set.has()` lookup (O(1)).
- **`getCountryCodesForCallingCode`** was iterating all ~250 countries
and calling `getCountryCallingCode()` on each one **every invocation**.
Replaced with a precomputed `Map<callingCode, CountryCode[]>` built once
at module load (O(1) per call).
Both functions are called from `transformPhonesValue` on every phone
field mutation, causing cumulative overhead visible in profiling (p95
self-time ~20-35ms).
## Test plan
- [ ] Verify phone field creation/update still works correctly (country
code validation, calling code resolution)
- [ ] Verify spreadsheet import with phone fields still validates
properly
- [ ] Confirm no regression in `isValidCountryCode` or
`getCountryCodesForCallingCode` behavior
Made with [Cursor](https://cursor.com)
Co-authored-by: Cursor <cursoragent@cursor.com>
Updated the `useCommandMenu`, `PageDragDropProvider`, and `useHandleAddToNavigationDrop` hooks to utilize the new `getSnapshotValue` utility for improved state access. This change enhances code clarity and consistency by reducing direct interactions with the Recoil state. Additionally, refactored the navigation drop handling logic to encapsulate repetitive code into a new function, `openEditForNewNavItem`, simplifying the process of opening edit modes for new navigation items.
Added a new Recoil atom `addToNavPayloadRegistryState` to manage the state of draggable items in the navigation menu. Updated the `CommandMenuItemWithAddToNavigationDrag` component to utilize this state for handling drag-and-drop operations. Refactored the drag update and drop handling logic in `PageDragDropProvider` and `useHandleAddToNavigationDrop` to leverage the new state management, improving the overall drag-and-drop functionality and ensuring better item tracking during interactions. Removed the deprecated utility functions related to draggable IDs.
Order should be
1. We delete all the file records (from core.file table)
2. We add the foreign key file / applicationId (pg constraint)
3. We further update the table structure: fullPath is deleted; path is
created; unicity constraint between workspaceId/applicationId/path is
created (pg constraint)
4. we migrate the workflow steps (this will create files in core.file)
5. we backfill the application package (same)
---------
Co-authored-by: Charles Bochet <charles@twenty.com>
## Summary
Fixes#17667
- **Root cause**: `ActivityQueryResultGetterHandler` called
`JSON.parse()` on the `blocknote` field and assumed the result was
always an array. When the stored value was valid JSON but not an array
(e.g., `"{}"`), `blocknote.map()` crashed with `blocknote.map is not a
function`, breaking the entire notes page.
- **Fix**: Replaced the object-level `ActivityQueryResultGetterHandler`
(hardcoded for `note`/`task` only) with a generic field-level
`RichTextV2FieldQueryResultGetterHandler` that safely parses blocknote
JSON with `Array.isArray` validation and gracefully skips malformed
values instead of crashing.
- **Bonus**: The new handler works for **all** objects with
`RICH_TEXT_V2` fields (not just `note`/`task`), following the same
pattern as the existing `FilesFieldQueryResultGetterHandler`.
## Changes
| File | Change |
|------|--------|
| `rich-text-v2-field-query-result-getter.handler.ts` | New field-level
handler with safe blocknote parsing |
| `common-result-getters.service.ts` | Register new handler, remove
`note`/`task` object handlers |
| `activity-query-result-getter.handler.ts` | Deleted (replaced by
field-level handler) |
| `rich-text-v2-field-query-result-getter.handler.spec.ts` | 9 tests
covering all edge cases |
## Test plan
- [x] Unit tests pass (9 tests covering: null blocknote, non-string
blocknote, invalid JSON, non-array JSON like `"{}"`, no images, external
URLs, internal URLs, multiple fields)
- [x] Lint passes (`lint:diff-with-main`)
- [x] Typecheck passes
Made with [Cursor](https://cursor.com)
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Added new components `CommandMenuAddToNavDraggablePlaceholder` and `CommandMenuAddToNavDroppable` to facilitate drag-and-drop interactions within the navigation menu. Updated existing components to integrate these new features, allowing users to rearrange items more intuitively. Enhanced the `CommandMenuItemWithAddToNavigationDrag` to support drag indices for better item positioning during drag operations. Refactored related components to ensure compatibility with the new drag-and-drop context.
Replaced 'objectNameSingular' with 'itemType' in the isLocationMatchingNavigationMenuItem utility to improve clarity and consistency. Updated corresponding tests to reflect this change, ensuring accurate navigation item matching based on item types.
Updated the logic for moving navigation menu items to account for folder structure. The changes ensure that items are moved within their respective folders, enhancing the accuracy of item positioning. This includes adjustments to how siblings are identified and managed during move operations.
# Introduction
Completely finalize the universal migration at builder and runner
levels.
Which mean that from now on the builder only compares
`universalFlatEntity` and produces universal workspace migration that
the runner ingest
## What's done
Migrated all create metadata remaining for
- agent
- skill
- commandMenuItem
- navigationMenuItem
- fieldMetadata
- objectMetadata
- view
- viewField
- viewFilter
- viewGroup
- viewFilterGroup
- index
- logicFunction
- role
- roleTarget
- pageLayout
- pageLayoutTab
- pageLayoutWidget
- rowLevelPermissionPredicate
- rowLevelPermissionPredicateGroup
- frontComponent
## Fix
Replaced direct database queries with existing flat entity map caches
for the two that already have cache infrastructure:
- **CallDatabaseEventTriggerJobsJob** - now uses flatLogicFunctionMaps
cache via WorkspaceCacheService.getOrRecompute(), filtering in memory
for non-deleted logic functions with databaseEventTriggerSettings.
- **CallWebhookJobsJob** - now uses flatWebhookMaps cache via
WorkspaceCacheService.getOrRecompute(), filtering in memory for webhooks
matching the event's operations.
- Note: **WorkflowDatabaseEventTriggerListener** - left as-is since
WorkflowAutomatedTriggerWorkspaceEntity extends BaseWorkspaceEntity (not
SyncableEntity) and has no flat entity map cache infrastructure yet.
Refactored the useWorkspaceSectionItems hook to always push folder items into the accumulator, removing the conditional check for defined folder children. This change streamlines the logic for handling navigation menu items, ensuring that all folder items are consistently processed.
Introduced unit tests for the recordIdentifierToObjectRecordIdentifier function, validating its behavior with various input scenarios. The tests ensure correct mapping of record identifiers to object records, including handling of avatar URLs and link generation for specific object types, enhancing overall code reliability.
Introduced unit tests for the filterWorkspaceNavigationMenuItems and getObjectMetadataForNavigationMenuItem utility functions. These tests validate the filtering of navigation menu items based on userWorkspaceId and ensure correct retrieval of object metadata for various item types, enhancing overall code reliability.
Enhanced the error messages for navigation menu item creation to specify that an external link is now a valid option. This change improves user guidance by clarifying the requirements for creating navigation menu items.
Introduced comprehensive unit tests for utility functions related to navigation menu items, including normalization of URLs, computation of insert indices and positions, and validation of workspace folder IDs. These tests enhance code reliability and ensure correct functionality across various scenarios.
## Summary
- **Extract `SecureHttpClientService`** from `tool` module into a
dedicated `core-modules/secure-http-client/` module with proper NestJS
module encapsulation
- **Fix module hygiene**: 12 modules that incorrectly listed
`SecureHttpClientService` as a direct provider now properly import
`SecureHttpClientModule`
- **Add structured logging** for outbound HTTP requests with
workspace/user context (for GuardDuty alert correlation)
- **Rename type files** to follow one-export-per-file convention
(`get-secure-axios-adapter.types.ts` ->
`secure-adapter-dependencies.type.ts`, new
`outbound-request-context.type.ts` / `outbound-request-source.type.ts`)
### Why
`SecureHttpClientService` is a cross-cutting concern (used by auth,
captcha, file upload, geo-map, telemetry, admin-panel, REST API, contact
creation, webhooks, and workflow tools) but was bundled inside the
`tool` module. Most consumers worked around this by listing it as a
direct provider instead of importing a module, which is fragile and not
idiomatic NestJS.
## Test plan
- [x] All 60 unit tests pass (`secure-http-client.service.spec.ts`,
`get-secure-axios-adapter.util.spec.ts`, `is-private-ip.util.spec.ts`)
- [x] Related module tests pass (admin-panel, contact-creation, tool)
- [x] `npx nx typecheck twenty-server` passes
- [x] `npx nx lint:diff-with-main twenty-server` passes
- [x] Server compiles and bootstraps successfully
Made with [Cursor](https://cursor.com)
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Included a link property set to null in various navigation menu item metadata creation functions. This change ensures that the link attribute is consistently defined across different components, improving data structure integrity and preparing for future enhancements.
Updated the AddToNavigationIconSlot component to check for valid icon types by allowing only strings, numbers, and booleans to return null. This change enhances the component's robustness by ensuring that only appropriate icon types are processed, preventing potential rendering issues.
Added folderUniversalIdentifier to the buildStandardFlatNavigationMenuItemMaps and createStandardNavigationMenuItemFolderFlatMetadata functions. This enhancement ensures that the folder's unique identifier is correctly incorporated into the navigation menu item metadata, improving data integrity and consistency across the application.
Modified the folderContentDropDisabled assignment to use isWorkspaceFolder, ensuring accurate handling of drop disabled states based on the current workspace context. This change improves the component's functionality and responsiveness to workspace conditions.
Updated the AddToNavigationIconSlot component to return null when the icon prop is not a function. This change improves the component's robustness by ensuring that only valid icon components are rendered, preventing potential runtime errors.
Updated the searchRecords assignment to safely access nested properties in searchData, ensuring robust handling of potential undefined values. This change improves the reliability of the component when processing search results.
Updated the comparison logic in the useSaveNavigationMenuItemsDraft hook to include targetRecordId, ensuring accurate detection of changes in navigation menu items. This enhancement improves the functionality of draft saving by considering all relevant identifiers.
Refactored the sorting logic for active non-system object metadata items in both CommandMenuNewSidebarItemPage and useFilteredObjectMetadataItems hooks. The sorting now utilizes the spread operator to create a new array before sorting, improving code clarity and consistency across components.
Modified the logic for displaying the contextual description in the CommandMenuItemWithAddToNavigationDrag component. The description now defaults to the provided description when not hovered, improving clarity and user experience.
Added a key prop using selectedItem.id to the CommandMenuEditLinkItemView component to ensure proper rendering and reconciliation of list items in React. This change enhances performance and prevents potential rendering issues when the selected item changes.
Refactored the CommandMenuNavigationMenuItemEditPage and related hooks to enhance item type checks and streamline component logic. Updated imports for better organization and clarity, ensuring consistent handling of navigation menu item types. This change simplifies the logic for rendering components based on selected item types, improving overall code readability and maintainability.
2026-02-10 14:08:37 +05:30
Abdul RahmanGitHubCopilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
Refactored the CommandMenuEditFolderPickerSubView to utilize a new custom hook, useFolderPickerSelectionData, for improved folder selection management. This change simplifies the component's logic by centralizing folder filtering and selection handling. Additionally, updated other components to use a new utility function for generating selectable item IDs, enhancing code consistency and readability.
Updated various components and hooks to utilize a centralized NAVIGATION_MENU_ITEM_TYPE constant for item type checks. This change enhances code consistency and readability by replacing string literals with a defined type, ensuring better maintainability and reducing the risk of errors in item type handling.
Eliminated the getNavigationMenuItemType utility function and replaced its usage with direct access to itemType properties in relevant components and hooks. This change simplifies the logic for determining navigation menu item types and enhances code clarity.
Updated the CommandMenuEditFolderPickerSubView and CommandMenuNavigationMenuItemEditPage components to replace individual item type flags with a single selectedItemType property. This change streamlines the logic for determining item types and enhances code readability. Additionally, removed unused flags from the useSelectedNavigationMenuItemEditData hook.
- Updated the migration command to include `link` and `icon` properties for navigation menu items, ensuring consistency across various components.
- Enhanced metadata creation utilities to support these properties, improving the overall functionality and representation of navigation menu items.
- Deleted the migration file that added a `link` column to the `navigationMenuItem` table, as it is no longer needed following recent updates to the database schema.
- Created a new migration to add `link` and `icon` columns to the `navigationMenuItem` table in the database.
- This enhancement supports the recent updates to navigation menu item components, allowing for better representation and functionality.
- Introduced `link` and `icon` properties to the `ALL_ENTITY_PROPERTIES_CONFIGURATION_BY_METADATA_NAME` constant, enhancing the metadata structure for better representation and functionality.
- Both properties are set to not be stringified and have undefined universal properties, maintaining consistency with existing configurations.
- Introduced an `icon` property across various navigation menu item components, including input types, DTOs, and entities, to enhance the representation of menu items.
- Updated utility functions to accommodate the new `icon` property, ensuring consistent handling during item creation and transformation.
- Enhanced metadata creation utilities to support the inclusion of icons, improving the overall functionality and user experience of the navigation menu.
Last upgrade PR left stale entries for transitive import of Axios.
Raising this PR to fix and ensure Axios 1.13.5 is the de-facto in
yarn.lock for consistency.
- Introduced `CommandMenuObjectViewRecordInfo` component to display information for selected view and record items in the command menu.
- Updated `CommandMenuPageInfo` to integrate the new component, improving the handling of view and record types.
- Enhanced `useSelectedNavigationMenuItemEditData` hook to include a flag for record items, supporting the new component's functionality.
- Introduced `onAddBefore` and `onAddAfter` props in `CommandMenuEditLinkItemView`, `CommandMenuEditObjectViewBase`, and `CommandMenuEditOrganizeActions` to support adding items before and after existing menu items.
- Updated `CommandMenuNavigationMenuItemEditPage` to include new actions for adding items, enhancing the command menu's functionality.
- Implemented context management for item insertion using Recoil, allowing for dynamic placement of new items in the navigation menu.
- Added new icons for the add actions to improve visual representation in the command menu.
- Created `addMenuItemInsertionContextState` and `AddMenuItemInsertionContext` types to manage the state related to item insertion, improving code organization and maintainability.
- Removed unused variables and props in `CommandMenuNewSidebarItemPage` and `CommandMenuNewSidebarItemViewPickerSubView`, streamlining the component logic.
- Simplified filtering logic for available object metadata items, enhancing performance and readability.
- Updated the structure of the components to improve maintainability and user experience in the command menu.
- Updated `StyledFolderDroppableContent` to replace `$isEditMode` and `$isEmpty` props with a single `$compact` prop, simplifying the padding logic based on the editing state and item presence.
- Adjusted the `WorkspaceNavigationMenuItemsFolder` component to utilize the new `$compact` prop, enhancing code clarity and maintainability.
Fix after resolveEntityRelationUniversalIdentifiers introduction
```
FlatEntityMapsException [Error]: Could not find rowLevelPermissionPredicateGroup for given rowLevelPermissionPredicateGroupId
at resolveEntityRelationUniversalIdentifiers
```
- Creating util for RLS flat entity creation to be aligned with other
entities
- Progressively update the flat entity maps as each new group is built,
following the same optimistic pattern used by
computeUniversalFlatEntityMapsFromTo in the validate build and run. The
updated maps are now passed to computePredicateOperations so predicates
can also resolve references to the newly created groups.
- Adding more coverage
Co-authored-by: Charles Bochet <charlesBochet@users.noreply.github.com>
- Pass the auth token to worker
- Fetch the file from the rest API with the auth token
- Create the blob url
- Update the stories to pass a fake token which will be ignored
---------
Co-authored-by: Charles Bochet <charlesBochet@users.noreply.github.com>
# What this PR does
Overall naming `universal` versus `flat` is not always the most updated
and so on
Will make a big cleaning tour after I've finished the whole migration
Migrating all `view` and ( filter fields etc ) `field` `object` `index`
to the universal pattern on all `services`, `builder` and `runner`
levels
## Universal and flat optimistic tooling
`addUniversalFlatEntityToUniversalFlatEntityAndRelatedEntityMapsThroughMutationOrThrow`
and its delete counterpart maintain the consistency of
`UniversalFlatEntityMaps` when an entity is created or removed. Beyond
inserting/removing the entity from its own maps, they walk through
`ALL_UNIVERSAL_METADATA_RELATIONS` to update the **aggregator arrays**
on related parent entities — the add appends the new entity's
`universalIdentifier` to the parent's aggregator (e.g. a new viewField's
identifier gets appended to its parent view's
`viewFieldUniversalIdentifiers`), and the delete filters it out. This
keeps the maps in sync so that diff computations and relation lookups
remain accurate throughout the migration building process.
## ALL_UNIVERSAL_METADATA_RELATIONS
`ALL_UNIVERSAL_METADATA_RELATIONS` is the universal counterpart of
`ALL_METADATA_RELATIONS`. It maps each metadata entity to its
many-to-one and one-to-many relations using universal foreign keys
(`*UniversalIdentifier`) instead of database IDs (`*Id`). This allows
migration actions to reference related entities in a workspace-agnostic
way. Relations that are workspace-specific (e.g. `workspace`,
`dataSource`, `userWorkspace`) are set to `null` and skipped during
resolution.
## `workspaceMigrationCreateIdEnrichment`
Reserved to API metadata ( will be able to validate at app installation
lvl )
- Workspace migration `create` actions now carry an optional `id` (and
`fieldIdByUniversalIdentifier` for object/field actions) so that
caller-provided IDs flow through the entire build-validate-run pipeline.
- New `enrichCreateWorkspaceMigrationActionsWithIds` utility resolves
`universalIdentifier → id` mappings after the builder runs and injects
them into the migration actions before the runner persists entities.
- Runner action handlers use the provided IDs instead of generating new
UUIDs, enabling deterministic entity creation for synchronization
workflows.
## `resolveUniversalUpdateRelationIdentifiersToIds`
`resolveUniversalUpdateRelationIdentifiersToIds` converts universal
identifiers (workspace-agnostic, stable keys) in a migration update
payload into concrete database UUIDs, so the update can be applied to a
specific workspace.
It iterates over the many-to-one relations defined in
`ALL_UNIVERSAL_METADATA_RELATIONS` for the given entity type, replaces
each `*UniversalIdentifier` property with its corresponding `*Id` by
looking up the target entity in `allFlatEntityMaps`, and throws if a
non-null identifier can't be resolved.
Used by all `update` action handlers in
`transpileUniversalActionToFlatAction`, avoiding duplicated resolution
logic across handlers.
## What this PR does not
- Migrating twenty-standard declaration to universal
- Migrating all the inputs transpilers to universal
- Migrating all metadata to be fully universal ( we still need to
de-scope the type of all of them and refactor their validator very close
)
---------
Co-authored-by: Charles Bochet <charles@twenty.com>
- Updated `StyledFolderDroppableContent` to accept new props `$isEditMode` and `$isEmpty`, allowing for dynamic padding adjustments based on the editing state and item presence.
- Integrated these props into the `WorkspaceNavigationMenuItemsFolder` component to improve layout responsiveness during editing, enhancing user experience.
- Simplified `CommandMenuEditObjectViewBase` by removing unnecessary props and components, enhancing clarity and maintainability.
- Deleted `CommandMenuEditViewPickerSubView` and related hooks to streamline the command menu structure, reducing complexity.
- Updated `CommandMenuNavigationMenuItemEditPage` to reflect changes in the object editing logic, improving overall functionality and user experience.
- Updated `CurrentWorkspaceMemberNavigationMenuItems` and `WorkspaceNavigationMenuItemsFolder` components to utilize `useNavigate` for improved navigation handling.
- Introduced logic to navigate to the first non-link item when the menu is closed, enhancing user experience and streamlining navigation.
- Refactored location handling to improve code clarity and maintainability across components.
- Updated the `NavigationItemDropTarget` component to support a new `compact` prop, allowing for dynamic height adjustment based on the editing state.
- Introduced `handleAddMenuItem` function in `WorkspaceNavigationMenuItems` to facilitate adding new items to the navigation menu.
- Enhanced `NavigationDrawerSectionForWorkspaceItems` to include an `onAddMenuItem` prop, enabling the display of an "Add menu item" option when in edit mode, improving user interaction and navigation management.
- Changed the border color in the StyledSearchContainer from medium to light, enhancing the overall appearance and alignment with the theme's design principles.
- Added a new `hasSubMenu` prop to the `CommandMenuNewSidebarItemViewObjectPickerSubView` component, enabling the display of submenu items for better organization and navigation.
- Updated the component's structure to improve user interaction and enhance the overall functionality of the command menu.
- Added logic to filter object metadata items based on displayable views, improving the selection process in the command menu.
- Introduced a new prop `showSystemObjectsOption` to conditionally render the system objects option in the sidebar, enhancing user experience.
- Updated related components to utilize the new filtering and display logic, ensuring a more intuitive interaction with the command menu items.
- Introduced `NAVIGATION_SECTIONS` constants to categorize navigation items into 'workspace' and 'favorites', improving code clarity and organization.
- Added `NavigationDragSourceContext` to manage the source droppable ID during drag-and-drop operations, enhancing state management.
- Updated various components to utilize the new context and constants, including `NavigationItemDropTarget`, `NavigationMenuItemDroppable`, and others, to improve drag-and-drop handling.
- Implemented logic to disable drop targets based on the current section, enhancing user experience during drag-and-drop interactions.
- These changes collectively improve the structure and functionality of the navigation menu, ensuring a more intuitive drag-and-drop experience.
- Updated the `createAddToNavigationDragPreview` function to wrap the `AddToNavigationDragPreview` component in a `RecoilRoot`, enabling state management for drag-and-drop operations.
- Adjusted the positioning of the drag preview element from off-screen to the top-left corner, improving visibility during drag actions.
- These changes enhance the functionality and user experience of the drag-and-drop feature within the navigation menu.
When syncing an app with relation multiple times, it would fail because
the relation fields could not be identified due to universalIdentifier
being overwritten at field cretion
- Introduces a SerializedEventData type that captures only serializable
properties from DOM/React events
- Updates `wrapEventHandler` in the host component registry to serialize
native events via serializeEvent() before passing them across the worker
boundary via postMessage, avoiding circular references and non-cloneable
DOM nodes
- Updates generated remote element event signatures to use
RemoteEvent<SerializedEventData> instead of bare RemoteEvent, giving
front-component authors typed access to event details
## Summary
- Fixes login failing with `Error: URL is required` when captcha (Google
reCAPTCHA or Turnstile) is enabled
- The secure axios adapter (`getSecureAxiosAdapter`) checked
`config.url` before `baseURL` resolution. In Axios, `baseURL + url`
combination happens inside the default HTTP adapter, not before custom
adapters are called. When captcha drivers configure a `baseURL` and call
`.post('', data)`, the empty string url was incorrectly treated as
missing.
- The adapter now resolves `baseURL` + `url` itself before validation,
matching the default Axios HTTP adapter behavior
## Test plan
- [x] Existing unit tests (23 tests) all pass
- [ ] Verify login works with captcha enabled (`CAPTCHA_DRIVER` set to
`google-recaptcha` or `turnstile`)
Made with [Cursor](https://cursor.com)
Co-authored-by: Cursor <cursoragent@cursor.com>
## Context
Ability to serve frontend component
See:
```typescript
curl -i 'http://localhost:3000/rest/front-components/35063b3f-bc4c-4358-8966-7762677802a3' \
--header 'Authorization: Bearer eyJhb...'
HTTP/1.1 200 OK
X-Powered-By: Express
Access-Control-Allow-Origin: *
Content-Type: application/javascript
Date: Mon, 09 Feb 2026 10:28:17 GMT
Connection: keep-alive
Keep-Alive: timeout=5
Transfer-Encoding: chunked
// react-globals:react/jsx-runtime
var jsx = globalThis.jsx;
var jsxs = globalThis.jsxs;
var Fragment = globalThis.React.Fragment;
// src/front-components/test.tsx
var RemoteComponents = globalThis.RemoteComponents;
var Component = () => {
return /* @__PURE__ */ jsxs(RemoteComponents.HtmlDiv, { style: { padding: "20px", fontFamily: "sans-serif" }, children: [
/* @__PURE__ */ jsx(RemoteComponents.HtmlH1, { children: "My new component!" }),
/* @__PURE__ */ jsx(RemoteComponents.HtmlP, { children: "This is your front component: test" })
] });
};
var test_default = globalThis.jsx(Component, {});
export {
test_default as default
};
//# sourceMappingURL=test.mjs.map
```
readFile_v2 returns a Node.js Stream object (Readable). Here we are
using stream pipeline which connects the readable stream (file) to the
writable stream (HTTP response) which efficiently streams the file
content directly to the HTTP response without loading the entire file
into memory. (in chunks, handling backpressure and closing the
connection when the file is fully sent)
- Renamed drag event handlers for clarity, changing `handleDocumentDrop` to `handleDocumentDragStart` and introducing `handleDocumentDragEnd` to manage drag state more effectively.
- Enhanced the logic for setting active drop targets based on the current draft items, improving user feedback during drag-and-drop operations.
- Updated event listener management to ensure proper cleanup, contributing to better performance and maintainability of the component.
- These changes collectively enhance the drag-and-drop experience within the navigation sidebar.
- Updated the `StyledIconSlot` component to use a `$hasFixedSize` prop instead of `$hasBackgroundColor`, enhancing the flexibility of the drag handle's appearance.
- Simplified icon size handling in `AddToNavigationDragHandle` and `IconWithBackground` components by standardizing the size to `theme.icon.size.md`, improving consistency across the application.
- These changes enhance the visual coherence of the navigation menu items and streamline icon rendering logic.
- Introduced the `IconWithBackground` component to standardize icon rendering with background colors, improving visual consistency across command menu items.
- Updated `CommandMenuItem`, `CommandMenuObjectMenuItem`, and related components to utilize the new `IconWithBackground`, enhancing their appearance and theming capabilities.
- Integrated theme-based icon color management in `CommandMenuNewSidebarItemViewObjectPickerSubView` and `CommandMenuNewSidebarItemViewSystemSubView`, ensuring better alignment with the overall design.
- These changes collectively improve the user interface and maintainability of the command menu components.
## Summary
- Wrap `AddFileEntityUniqueConstraint` migration in a SAVEPOINT
try-catch so it doesn't break when duplicate `(workspaceId,
applicationId, path)` rows exist in `core.file`
- Extend `DeleteFileRecordsCommand` upgrade command to add the unique
constraint after cleaning up file records
## PR Description
This PR:
- Introduces a `FrontComponentHostCommunicationApi`, which allows us to
pass functions to be executed from the worker
- Exposes a navigate function from the host to the front component
remote workers, enabling SDK components to trigger in-app navigation
- Gets rid of `useSyncExternalStore` and makes the execution context
reactive without it
- Refactors and improves the `esbuild` plugin system
## Video
https://github.com/user-attachments/assets/7b26a1c2-f85f-4898-a71d-f60c70e61711
⚠️ **AI-generated PR — not ready for review** ⚠️
cc @FelixMalfait
---
## Changes
### System prompt improvements
- Explicit skill-before-tools workflow to prevent the model from calling
tools without loading the matching skill first
- Data efficiency guidance (default small limits, use filters)
- Pluralized `load_skill` → `load_skills` for consistency with
`load_tools`
### Token usage reduction
- Output serialization layer: strips null/undefined/empty values from
tool results
- Lowered default `find_*` limit from 100 → 10, max from 1000 → 100
### System object tool generation
- System objects (calendar events, messages, etc.) now generate AI tools
- Only workflow-related and favorite-related objects are excluded
### Context window display fix
- **Bug**: UI compared cumulative tokens (sum of all turns) against
single-request context window → showed 100% after a few turns
- **Fix**: Track `conversationSize` (last step's `inputTokens`) which
represents the actual conversation history size sent to the model
- New `conversationSize` column on thread entity with migration
### Workspace AI instructions
- Support for custom workspace-level AI instructions
---------
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
- Updated the `StyledIconSlot` to accept an optional `$backgroundColor` prop, allowing for greater customization of the drag handle's appearance.
- Simplified the cursor handling logic and adjusted padding and width based on the presence of the background color, improving the component's flexibility.
- Enhanced the `AddToNavigationDragHandle` props to include `iconBackgroundColor`, providing more control over the icon's styling during drag operations.
- These changes improve the usability and visual consistency of the drag handle within the navigation menu.
- Integrated the `useLingui` hook to provide internationalization support for the drag-and-drop functionality within the `CommandMenuItemWithAddToNavigationDrag` component.
- Updated the description displayed during hover to show a contextual message, improving user guidance when dragging items to the navigation bar.
- These changes enhance the user experience by providing clearer instructions during drag operations.
- Simplified the icon rendering logic by replacing the custom `isIconComponent` type guard with `isValidElement` to streamline the component's functionality.
- Enhanced the handling of icon components to ensure proper rendering based on their type, improving code clarity and maintainability.
- These changes contribute to a more efficient and understandable implementation of the AddToNavigationIconSlot component.
- Replaced `IconFolderPlus` with `IconFolderSymlink` in the `CommandMenuEditOrganizeActions` component to better represent the action of moving items to a folder.
- Added `IconFolderSymlink` to the `twenty-ui` display module, ensuring it is available for use across the application.
- These changes enhance the visual representation of actions within the command menu, improving user experience and clarity.
## Summary
- Normalize all file paths with `path.resolve` instead of `join` to
properly handle `..` segments in file path inputs
- Add `assertPathIsWithinStorage` guard on all write, delete, move,
copy, and existence-check operations
- Introduce `ACCESS_DENIED` exception code with i18n-ready user-friendly
message
- Read path already had realpath-based validation; updated its error
code to `ACCESS_DENIED` for consistency
## Test plan
- [x] Typecheck passes
- [x] Lint passes
- [x] Manual: verify file upload/download still works with valid paths
- [x] Manual: verify `../` in file paths is rejected with ACCESS_DENIED
Made with [Cursor](https://cursor.com)
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Etienne <etiennejouan@users.noreply.github.com>
Fix#17711
### Reproduce steps
1. import this xlsx file
[test-import.xlsx](https://github.com/user-attachments/files/25156642/test-import.xlsx)
2. import company worksheet at first and then import people worksheet
3. you will see an error <img width="324" height="101"
alt="Snipaste_2026-02-07_19-22-04"
src="https://github.com/user-attachments/assets/bf2703da-a57a-4795-805b-6ddcc689c621"
/>
And I found neither the frontend nor the backend applies lowercase
normalization to the query value. Although the standard UI input always
displays company links in lowercase, user might mixed the case in their
xlsx/csv bulk import. So I did a simple check in frontend.
### Additional findings:
1. Email has the same case sensitivity issue when opportunities import.
You can test same as in test-import.xlsx file (I created a opportunities
worksheet. It will have same issue: <img width="330" height="101"
alt="email error"
src="https://github.com/user-attachments/assets/db36b19b-2abe-4c61-a661-f8d1eb357a5e"
/>
2. API also has: I also tested via the GraphQL API Playground and
confirmed that createOnePerson with a mixed-case URL fails to find an
existing company. <img width="1419" height="513"
alt="Snipaste_2026-02-07_23-33-56"
src="https://github.com/user-attachments/assets/c189f1fd-9793-42d5-a1a9-616cffd2592e"
/>
### Approach:
1. Only change it in frontend, and I will add email check later; (my
current commit)
2. Or backend: Normalize values in computeUniqueConstraintCondition in
twenty-server/src/engine/twenty-orm/utils/compute-relation-connect-query-configs.util.ts
(which handles connect.where queries, and this will cover xlsx import
and api import: createone, createmany, updatemany, updateone. And much
better for future extension.
(Personally, I'd prefer the backend approach. But I'd love to hear your
thoughts on which approach you'd prefer, and whether my analysis is on
the right track.
---------
Co-authored-by: Etienne <45695613+etiennejouan@users.noreply.github.com>
- Introduced a new styled component, `StyledFolderExpandableWrapper`, to improve layout management during drag-and-drop operations.
- Updated the rendering logic within `WorkspaceNavigationMenuItemsFolder` to enhance clarity and maintainability by utilizing the new styled component.
- Simplified the handling of navigation menu items by directly integrating the `Droppable` component, ensuring a more efficient drag-and-drop experience.
- These changes collectively enhance the user experience and maintain the integrity of the navigation menu during interactions.
- Introduced the `WorkspaceNavigationMenuItemFolderDragClone` component to enhance the drag-and-drop experience within the workspace navigation menu.
- Integrated the new component into the `WorkspaceNavigationMenuItemsFolder` to render a clone of the draggable item, improving user interaction during drag operations.
- This addition streamlines the drag-and-drop functionality, providing visual feedback and maintaining the integrity of the navigation menu items during dragging.
- Updated the parameter name in the `handleDragStart` function from `_` to `_dragStart` for better clarity and understanding of its purpose.
- This change enhances code readability and maintainability by providing a more descriptive parameter name.
- Removed the unused `NavigationItemDropTarget` import to streamline the component.
- Introduced a new styled component, `StyledFolderDroppableContent`, to enhance layout consistency.
- Simplified the rendering logic by directly using `NavigationDrawerItem` and `DraggableItem`, improving code clarity and maintainability.
- These changes contribute to a more organized and efficient implementation of the workspace navigation menu items.
- Introduced the `NavbarDragProvider` component to manage drag-and-drop context for navigation items, enhancing user interaction capabilities.
- Updated `MainNavigationDrawerScrollableItems` and `CurrentWorkspaceMemberFavoritesFolders` components to utilize the new drag provider, improving the organization of draggable items.
- Refactored navigation menu item components to replace the previous drag provider with a more streamlined approach, enhancing code clarity and maintainability.
- Adjusted constants for droppable IDs to support the new drag-and-drop logic, ensuring consistency across the navigation menu items.
- These changes collectively enhance the user experience by enabling intuitive drag-and-drop functionality within the navigation drawer.
- Added a new prop `alwaysShowRightIcon` to the `NavigationDrawerSectionTitle` component, allowing the right icon to remain visible regardless of mobile state.
- Updated the `StyledRightIcon` component to conditionally render opacity based on the new prop, improving user experience and accessibility.
- These changes enhance the flexibility and usability of the navigation drawer component.
- Adjusted icon sizes in multiple components to use a consistent spacing value of `3.5`, enhancing visual uniformity across the application.
- Updated the dimensions of the `StyledNavigationMenuItemIconContainer` to `4.5`, ensuring alignment with the new icon sizing.
- These changes improve the overall aesthetic and maintainability of the UI components.
- Eliminated the unused `isDefined` import from the `useSelectedNavigationMenuItemEditData` hook, enhancing code cleanliness and maintainability.
- This change contributes to a more organized codebase by removing unnecessary dependencies.
- Updated the `filterWorkspaceNavigationMenuItems` function to utilize the `NavigationMenuItem` type, improving type consistency and clarity in filtering logic.
- This change enhances the maintainability and readability of the code by ensuring that the function operates on a well-defined type.
- Updated various components and hooks to utilize the new `itemType` property for improved clarity in navigation menu item handling.
- Removed unnecessary imports and simplified condition checks, enhancing code readability and maintainability.
- These changes contribute to a more organized and efficient implementation of navigation menu item logic.
## Summary
- Removes unreachable dead code in `verifyJwtToken` — a legacy API key
verification block where the condition (`!payload.type && type ===
API_KEY`) was logically impossible. Also removes the now-unused
`isLegacyApiKey` parameter and `generateAppSecretLegacy` method.
- Adds explicit token type validation after JWT decode in
`verifyLoginToken`, `verifyRefreshToken`, and `verifyTransientToken`.
Each function now rejects tokens whose `type` field doesn't match what's
expected (defense-in-depth — the HMAC secret already binds the type, but
this makes the contract explicit).
## Test plan
- [x] Updated existing specs for login-token, refresh-token, renew-token
services — all 16 tests passing
- [x] Lint clean
Made with [Cursor](https://cursor.com)
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
## Summary
- Deletes `use-throttler.ts` — an envelop plugin that was never
registered in the GraphQL config plugin chain (dead code since
introduction)
- Removes the `graphql-rate-limit` dependency from `package.json` (only
consumer was the deleted file)
API rate limiting continues to work via `ThrottlerService` in the query
runner layer (for API key auth). Global coverage should be handled at
the infrastructure level (Cloudflare).
## Test plan
- [x] Confirmed no imports of `useThrottler`, `ThrottlerPluginOptions`,
or `UnauthenticatedError` from this file exist anywhere in the codebase
- [x] `graphql-rate-limit` has no other consumers
Made with [Cursor](https://cursor.com)
Co-authored-by: Cursor <cursoragent@cursor.com>
## Summary
- Renames `REFRESH_TOKEN_COOL_DOWN` to
`REFRESH_TOKEN_REUSE_GRACE_PERIOD` — the old name was misleading and
suggested a security mechanism rather than what it actually is: a grace
period for concurrent refresh token use (e.g. two browser tabs
refreshing simultaneously).
- Makes the token revocation in `renew-token.service.ts` conditional
(`revokedAt: IsNull()`), so if the token was already revoked by a
concurrent request, the original `revokedAt` timestamp is preserved and
the grace window stays anchored.
- Updates comments and config description to clarify intent.
## Test plan
- [x] Existing unit tests updated and passing
(`refresh-token.service.spec.ts`, `renew-token.service.spec.ts`)
- [x] Lint clean
Made with [Cursor](https://cursor.com)
Co-authored-by: Cursor <cursoragent@cursor.com>
## Summary
- Validates that the timezone parameter in group-by date expressions is
a recognized IANA timezone
- Adds SQL string literal escaping as a defense-in-depth measure for the
timezone value interpolated into SQL expressions
- Moves the `IANA_TIME_ZONES` constant to `twenty-shared` so it can be
reused across frontend and server packages
- Adds `INVALID_TIMEZONE` error code mapped to 400 Bad Request in both
GraphQL and REST API exception handlers
## Test plan
- [x] Unit tests for `validateIanaTimeZone` (valid IANA zones, fixed
offsets, rejects invalid strings)
- [x] Unit tests for `escapeSqlStringLiteral`
- [x] Integration tests for `getGroupByExpression` covering timezone
validation and granularity handling
Made with [Cursor](https://cursor.com)
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
- Updated condition checks in `useSelectedNavigationMenuItemEditData`, `WorkspaceNavigationMenuItemsFolder`, and `useWorkspaceSectionItems` to utilize the new `itemType` property for better clarity and consistency.
- Removed unnecessary imports and simplified logic related to navigation menu item types, enhancing code readability and maintainability.
- These changes contribute to a more organized and efficient implementation of navigation menu item handling.
- Introduced a new `NavigationMenuItemType` type to categorize menu items as 'folder', 'link', 'object', 'record', or 'view'.
- Updated the `ProcessedNavigationMenuItem` type to include an `itemType` property, improving clarity in item categorization.
- Refactored the `sortNavigationMenuItems` function to assign the appropriate `itemType` based on the item being processed, enhancing the sorting logic and maintainability.
- Simplified condition checks in `NavigationDrawerItemForObjectMetadataItem` to utilize the new `itemType` property for determining item characteristics, improving code readability.
- Updated the CSS syntax in the `StyledIcon` component to use the `css` template literal for better readability and maintainability.
- This change enhances the clarity of the styling logic within the component, contributing to a more organized codebase.
- Updated the `isIconComponent` function to allow for both function and object types, improving flexibility in icon handling.
- Renamed the local variable from `IconComponent` to `Icon` for clarity in rendering.
- These changes contribute to a more robust and maintainable implementation of the AddToNavigationIconSlot component.
- Replaced the `useNavigationMenuEditModeActions` hook with direct state management using Recoil's `useSetRecoilState` in `NavigationMenuEditModeBar` and `WorkspaceNavigationMenuItems` components, enhancing clarity and modularity.
- Introduced a new `cancelEditMode` function to handle the cancellation of edit mode, improving the organization of state updates.
- Removed the now-unnecessary `useNavigationMenuEditModeActions` hook, streamlining the codebase and reducing complexity.
- These changes contribute to a more efficient and maintainable implementation of navigation menu edit mode functionality.
- Simplified the calculation of new positions in the `useHandleNavigationMenuItemDragAndDrop` hook by removing unnecessary rounding, enhancing code readability.
- Updated the `calculateNewPosition` utility to consistently return rounded values, improving the accuracy of position calculations during drag and drop operations.
- These changes contribute to a more efficient and maintainable implementation of drag and drop functionality in the navigation menu.
- Replaced the `useUpdateNavigationMenuItemsDraft` hook with more specific hooks: `useUpdateFolderNameInDraft`, `useUpdateLinkInDraft`, and `useUpdateObjectInDraft`, enhancing clarity and modularity.
- Updated components to utilize the new hooks, streamlining draft management for folders, links, and objects.
- Introduced new hooks for adding items to the navigation menu draft, improving the organization and maintainability of the codebase.
- These changes contribute to a more efficient and structured implementation of command menu item handling.
- Updated `CommandMenuItemWithAddToNavigationDrag` and related components to replace the `Icon` prop with a unified `icon` prop, allowing for both `IconComponent` and `ReactNode` types.
- Introduced `AddToNavigationIconSlot` to encapsulate icon rendering logic, improving code clarity and reusability.
- These changes enhance the consistency and maintainability of the command menu item components.
- Streamlined the rendering logic by consolidating condition checks for object, link, and folder items, improving code clarity and reducing redundancy.
- Removed unnecessary props and simplified the return statements for better maintainability.
- These changes contribute to a more organized and efficient implementation of the CommandMenuNavigationMenuItemEditPage component.
- Updated `CommandMenuEditViewPickerSubView` to utilize local state for managing `currentDraft` and `objectMetadataItems`, enhancing clarity and reducing reliance on external props.
- Simplified the `CommandMenuNavigationMenuItemEditPage` by removing unnecessary props and streamlining the rendering logic for improved maintainability.
- Enhanced `CommandMenuNewSidebarItemPage` and related components by consolidating draft handling and removing unused imports, contributing to a more organized implementation of the command menu components.
- Simplified the rendering logic in `CommandMenuNewSidebarItemPage` by removing unnecessary props from `CommandMenuNewSidebarItemRecordSubView`, improving clarity.
- Introduced `CommandMenuNewSidebarItemRecordItem` to encapsulate record item rendering, enhancing reusability and maintainability.
- Updated `CommandMenuNewSidebarItemRecordSubView` to utilize the new `CommandMenuNewSidebarItemRecordItem`, streamlining the component structure.
- These changes contribute to a more organized and efficient implementation of the command menu components.
- Simplified the rendering logic in `CommandMenuNavigationMenuItemEditPage` by consolidating the object view rendering into a single inline function, enhancing clarity and reducing redundancy.
- Updated the condition checks for rendering the object view, streamlining the component's structure and improving maintainability.
- These changes contribute to a more organized and efficient implementation of the CommandMenuNavigationMenuItemEditPage component.
- Updated `CommandMenuEditViewPickerSubView` to integrate local handling of view selection, improving clarity and reducing reliance on external props.
- Introduced a new `handleSelectView` function to streamline view selection and state management.
- Utilized `useRecoilValue` for managing the selected navigation menu item in edit mode, enhancing state management and code maintainability.
- These changes contribute to a more organized and efficient implementation of the CommandMenuEditViewPickerSubView component.
- Updated `CommandMenuEditFolderPickerSubView` to integrate folder selection handling directly within the component, improving clarity and reducing reliance on external props.
- Removed the `onSelectFolder` prop and replaced it with a local `handleSelectFolder` function to streamline folder selection and state management.
- These changes enhance the organization and maintainability of the CommandMenuEditFolderPickerSubView component.
- Updated `CommandMenuEditFolderPickerSubView` and `CommandMenuNavigationMenuItemEditPage` to utilize `useRecoilValue` for managing the selected navigation menu item in edit mode, improving state management.
- Simplified the retrieval of selected item data by restructuring the hooks, enhancing code clarity and maintainability.
- These changes contribute to a more organized and efficient implementation of command menu components.
- Simplified the usage of `useNavigationMenuItemEditOrganizeActions` by destructuring its properties directly in the component.
- Updated the component to use the destructured properties for action handling, enhancing code clarity and reducing redundancy.
- These changes contribute to a more organized and maintainable implementation of the CommandMenuNavigationMenuItemEditPage component.
- Updated `CommandMenuNewSidebarItemPage`, `useNavigationMenuItemEditFolderData`, and `NavigationSidebarNativeDropZone` to utilize `useRecoilValue` for improved state management of `navigationMenuItemsDraft`.
- Simplified the retrieval of navigation menu items draft state across components, enhancing code clarity and maintainability.
- These changes contribute to a more organized and efficient implementation of navigation menu item handling.
- Removed unused imports and consolidated rendering logic in `CommandMenuNavigationMenuItemEditPage` and `CommandMenuNewSidebarItemPage` to enhance clarity.
- Introduced `CommandMenuObjectPickerItem` to standardize object menu item rendering across different components, improving code reusability.
- Updated `CommandMenuObjectPickerSubView` and `CommandMenuSystemObjectPickerSubView` to utilize the new `CommandMenuObjectPickerItem`, streamlining the rendering process.
- These changes enhance the organization, maintainability, and readability of the command menu components.
- Introduced hooks `useNavigationMenuItemEditFolderData` and `useSelectedNavigationMenuItemEditData` to streamline data handling in `CommandMenuEditFolderPickerSubView`, `CommandMenuEditOwnerSection`, and `CommandMenuEditViewPickerSubView`.
- Simplified state management and search functionality within `CommandMenuEditFolderPickerSubView` by utilizing local state for search input.
- Improved the logic for determining application IDs in `CommandMenuEditOwnerSection` based on the current draft and selected item.
- These changes enhance the organization, maintainability, and clarity of the command menu components.
- Introduced new hooks for managing navigation menu item edit data, including `useNavigationMenuItemEditFolderData`, `useNavigationMenuItemEditObjectPickerData`, `useNavigationMenuItemEditOrganizeActions`, `useNavigationMenuItemEditSubView`, and `useSelectedNavigationMenuItemEditData`.
- Simplified the CommandMenuNavigationMenuItemEditPage component by removing unused imports and consolidating state management logic.
- These changes improve the organization, maintainability, and clarity of the navigation menu item editing functionality.
- Simplified subViewHandlers by converting them into individual functions for better clarity.
- Updated onBack and onOpen functions to directly reference the new individual handlers, enhancing code organization.
- These changes contribute to a more maintainable and readable implementation of the CommandMenuNavigationMenuItemEditPage component.
## Context
- Add missing conditional display
- Set default tab as null for most of the page layout, letting the FE
handle that
- Fix duplicate "Note" widget in both task and note pages
- Simplify typing, removing redundant layoutName
- Replaced the reduce method with a for-of loop for better clarity in the getDescendantFolderIds function.
- Enhanced the logic for accumulating descendant folder IDs, improving maintainability and readability.
These changes contribute to a more organized implementation of the CommandMenuEditFolderPickerSubView component.
# Introduction
Requiring the spreaded `__universal` record that aggregates all the
universal identifier ( relations fk and aggregators ) of an entity to
its root
It's blockin for https://github.com/twentyhq/twenty/pull/17687 to be
finalized because if we don't we would have to migrated all related
entities at once in order for them to always have the universal
properties
## `resolveEntityRelationUniversalIdentifiers`
Introduced `resolveEntityRelationUniversalIdentifiers` a centralized
utility that resolves foreign key IDs to universal identifiers using
ALL_METADATA_RELATIONS metadata. It provides strict typing for both
input (foreign keys) and output (universal identifiers), with
nullability dynamically inferred from entity relation types.
Strictly and dynamically typed for both output and input
To do so added a new type and const/runtime grain to
ALL_METADATA_RELATIONS `isNullable`to many-to-one entries, derived from
the entity relation property types. And fixed incorrectly typed typeorm
entities
### Usage
```ts
const {
availabilityObjectMetadataUniversalIdentifier,
frontComponentUniversalIdentifier,
} = resolveEntityRelationUniversalIdentifiers({
metadataName: 'commandMenuItem',
foreignKeyValues: {
availabilityObjectMetadataId:
createCommandMenuItemInput.availabilityObjectMetadataId,
frontComponentId: createCommandMenuItemInput.frontComponentId,
},
flatEntityMaps: { flatObjectMetadataMaps, flatFrontComponentMaps },
});
```
On iterator creation, we get an error "Bad configuration". Looks like a
race condition with generation that happens before apollo cache gets
updated. Adding defensive checks
**What this PR does:**
- Migrates existing `Favorite` and `FavoriteFolder` entities to the new
`NavigationMenuItem` structure
- Preserves user-level vs workspace-level ownership
- Preserves folder structure, positions, and relationships
- Handles both view-based favorites (linked to views) and record-based
favorites (linked to records)
- Soft-deletes original favorites and folders after successful migration
- Enables the `IS_NAVIGATION_MENU_ITEM_ENABLED` feature flag
post-migration
- Skips migration if the feature flag is already enabled
- Idempotent: checks for existing navigation menu items to prevent
duplicates
**Migration flow:**
1. Migrate favorite folders first (creates folder mapping)
2. Migrate favorites
3. Soft-delete migrated favorites and folders
4. Enable feature flag
**What's next:**
- After all workspaces are migrated and the navigation menu item feature
is fully rolled out, we can:
- Remove the old `Favorite` and `FavoriteFolder` entities and related
code
- Remove the feature flag check and make navigation menu items the
default
- Clean up any deprecated favorites-related code paths in the frontend
---------
Co-authored-by: Etienne <45695613+etiennejouan@users.noreply.github.com>
## Context
I've seen errors in twenty-emails build where I18n from @lingui/core was
resolved to two different declaration files
```typescript
src/components/BaseEmail.tsx:20:19 - error TS2719: Type 'import("/Users/weiko/Projects/twenty/node_modules/@lingui/core/dist/index").I18n' is not assignable to type 'import("/Users/weiko/Projects/twenty/node_modules/@lingui/core/dist/index").I18n'. Two different types with this name exist, but they are unrelated.
Types have separate declarations of a private property '_locale'.
20 <I18nProvider i18n={i18nInstance}>
~~~~
node_modules/@lingui/react/dist/shared/react.b2b749a9.d.ts:42:5
42 i18n: I18n;
~~~~
The expected type comes from property 'i18n' which is declared here on type 'IntrinsicAttributes & Omit<I18nContext, "_"> & { children?: ReactNode; }'
```
Seems to be related to https://github.com/twentyhq/twenty/pull/17380
The tsconfig simplification changed how vite plugin resolves types during build. With the inherited moduleResolution: "node", the plugin and source code resolved @lingui/react's types differently, creating two incompatible I18n types from the same package.
## Fix
Add moduleResolution: "bundler" to packages/twenty-emails/tsconfig.json, aligning with twenty-front, twenty-ui, twenty-shared should fix the issue
## Summary
When a custom object is renamed after creation, the relation fields on
`noteTarget`, `taskTarget`, `attachment`, and `timelineActivity` keep
their old names but point to the renamed object.
For example:
- User creates custom object `solution`
- System creates field `solution` on NoteTarget → pointing to Solution
- User renames object from `solution` to `productCatalog`
- Field name stays as `solution` but points to `productCatalog`
- Migration runs: `solution` → `targetSolution`
- Now `targetSolution` points to `productCatalog` ❌
This causes the morph name computation to fail:
- `getMorphNameFromMorphFieldMetadataName("targetSolution",
"productCatalog")`
- Tries to remove `ProductCatalog` from `targetSolution` → no match
- Name stays as `targetSolution` instead of becoming `target`
- Frontend computes: `targetSolution` + `Company` =
`targetSolutionCompany` 💥
## The Fix
This command finds and fixes all such mismatches by:
1. Finding MORPH_RELATION fields where `field.name !=
target${capitalize(targetObject.nameSingular)}`
2. Renaming the column in the workspace schema
3. Updating the field metadata (name + joinColumnName in settings)
4. Invalidating the cache
## Usage
**Dry run (to see what would be fixed):**
```bash
npx nx run twenty-server:command upgrade:1-17:fix-morph-relation-field-names -- --workspace-id <id> --dry-run
```
**Fix a specific workspace:**
```bash
npx nx run twenty-server:command upgrade:1-17:fix-morph-relation-field-names -- --workspace-id <id>
```
**Fix all workspaces:**
```bash
npx nx run twenty-server:command upgrade:1-17:fix-morph-relation-field-names
```
## SQL to find affected workspaces
```sql
SELECT
fm."workspaceId",
COUNT(*) AS mismatched_fields
FROM core."fieldMetadata" fm
JOIN core."objectMetadata" target_om ON fm."relationTargetObjectMetadataId" = target_om.id
JOIN core."objectMetadata" source_om ON fm."objectMetadataId" = source_om.id
WHERE fm.type = 'MORPH_RELATION'
AND fm.name LIKE 'target%'
AND source_om."nameSingular" IN ('noteTarget', 'taskTarget', 'attachment', 'timelineActivity')
GROUP BY fm."workspaceId"
HAVING COUNT(*) FILTER (
WHERE fm.name != 'target' || initcap(replace(target_om."nameSingular", '_', ''))
) > 0;
```
- Introduced the `filterWorkspaceNavigationMenuItems` utility to streamline the filtering of navigation menu items based on user workspace ID.
- Updated multiple hooks (`useNavigationMenuEditModeActions`, `useNavigationMenuItemsDraftState`, `usePrefetchedNavigationMenuItemsData`, and `useSaveNavigationMenuItemsDraft`) to use the new utility for improved code clarity and maintainability.
- These changes enhance the organization and readability of the navigation menu item handling logic.
- Introduced helper functions `getMaxPosition` and `normalizeUrl` to streamline logic and enhance readability.
- Replaced inline logic with these helper functions in multiple locations to reduce code duplication.
- These changes contribute to a more organized and maintainable implementation of the useAddToNavigationMenuDraft hook.
- Reorganized import statements for better clarity and consistency.
- Simplified the logic for updating open folder IDs to enhance readability.
- Updated the conditional check for edit mode click handling to ensure proper functionality.
These changes contribute to a more organized and maintainable implementation of the WorkspaceNavigationMenuItemsFolder component.
- Reorganized import statements for improved clarity and consistency.
- Updated the conditional check in the handleDrop function for better readability.
These changes contribute to a more structured and maintainable implementation of the NavigationSidebarNativeDropZone component.
- Consolidated conditional rendering into a switch statement for better readability and maintainability.
- Updated the handling of folder and link additions to streamline the logic and enhance code organization.
- Reorganized import statements for improved clarity and consistency.
These changes contribute to a more efficient and structured implementation of the NavigationSidebarNativeDropZone component.
- Consolidated conditional rendering into a switch statement for improved clarity and maintainability.
- Streamlined the rendering logic for various sidebar item views, enhancing code organization.
- These changes contribute to a more efficient and structured implementation of the CommandMenuNewSidebarItemPage component.
- Integrated the `useTheme` hook to dynamically adjust icon sizes based on the theme.
- Updated the icon size assignment to utilize theme values, enhancing consistency in styling.
- These changes contribute to a more organized and visually coherent implementation of the NavigationMenuEditModeBar component.
- Removed unused import statements and consolidated drop target properties directly within the component.
- Simplified the drop target ID generation logic for better readability.
- Updated the drop target attributes to use data attributes directly in the JSX, enhancing clarity.
These changes contribute to a more organized and efficient implementation of the NavigationItemDropTarget and NavigationSidebarNativeDropZone components.
- Reorganized import statements for better code clarity and consistency.
- Simplified the rendering logic by consolidating the object menu item rendering into a dedicated function.
- Updated filtering logic to enhance readability and maintainability.
These changes contribute to a more efficient and organized implementation of the CommandMenuNewSidebarItemPage component.
- Removed redundant imports to streamline the component.
- Updated conditional rendering logic to enhance readability and maintainability.
- Consolidated rendering logic for object and view items into a single function, improving code organization.
These changes contribute to a more efficient and organized implementation of the CommandMenuNavigationMenuItemEditPage component.
- Added the `useRecoilValue` import to manage state more effectively.
- Updated the icon variable name from `PaintIcon` to `IconPaint` for better clarity.
- Reintroduced conditional rendering for the navigation menu edit mode bar to enhance readability.
These changes contribute to a more organized and maintainable implementation of the NavigationMenuEditModeBar component.
- Moved the import statement for `ReactNode`, `useContext`, and `useRef` to enhance code organization.
- Adjusted the import order to maintain consistency with other components.
- These changes contribute to a more efficient and organized implementation of the NavigationItemDropTarget component.
- Removed the `NavigationDrawerItemForLink` component and integrated its functionality directly into the `NavigationDrawerSectionForWorkspaceItems` component, enhancing code clarity and reducing complexity.
- Updated the rendering logic to utilize the `NavigationDrawerItem` component, streamlining the handling of link items and improving maintainability.
- Enhanced icon color handling by incorporating the `getNavigationMenuItemIconColors` utility, ensuring consistent styling across navigation items.
These changes contribute to a more efficient and organized implementation of the navigation drawer components.
- Introduced the `isDefined` utility function to enhance the clarity of icon rendering logic.
- Simplified padding assignments in the styled component for better readability.
- Streamlined the conditional rendering of icons to improve the component structure.
These changes contribute to a more organized and efficient implementation of the AddToNavigationDragPreview component.
- Introduced utility function `isDefined` to enhance the clarity of icon rendering logic.
- Simplified icon size and stroke assignments by storing them in variables, improving code maintainability.
- Updated conditional rendering for icons to streamline the component structure.
These changes contribute to a more organized and efficient implementation of the AddToNavigationDragHandle component.
- Replaced the `CommandMenuSelectObjectForViewMenuItem` component with a more streamlined implementation using `SelectableListItem` and `CommandMenuItem`, improving rendering efficiency and clarity.
- Integrated the `useIcons` hook to manage icon retrieval, enhancing code maintainability.
- Updated the `CommandMenuNewSidebarItemViewObjectPickerSubView` and `CommandMenuNewSidebarItemViewSystemSubView` components to utilize the new rendering approach, ensuring consistency across the command menu.
These changes contribute to a more efficient and organized implementation of the command menu components.
- Simplified the click handling logic by removing unnecessary checks and directly returning if `defaultViewId` is not defined.
- Enhanced the rendering logic for `CommandMenuItem` components by consolidating the payload structure and ensuring the `disabled` prop is set correctly.
These changes contribute to a more efficient and maintainable implementation of the command menu components.
- Integrated the `useFilteredPickerItems` custom hook to streamline filtering logic and enhance code clarity.
- Simplified the handling of no results text and selectable item IDs, improving overall component structure.
These changes contribute to a more efficient and maintainable implementation of the command menu components.
- Replaced manual filtering logic with the `useFilteredPickerItems` custom hook, enhancing code clarity and maintainability.
- Streamlined the handling of no results text and improved the rendering of selectable list items.
These changes contribute to a more efficient and organized implementation of the command menu components.
- Replaced the manual filtering logic with the `useFilteredPickerItems` custom hook, enhancing code clarity and maintainability.
- Streamlined the handling of selectable item IDs and no results text, improving the overall structure of the component.
These changes contribute to a more efficient and organized implementation of the command menu components.
- Simplified the logic for filtering non-readable object metadata items by consolidating the permission check into a single line, enhancing code clarity and maintainability.
- Removed redundant variable assignments to streamline the component structure.
These changes contribute to a more efficient and organized implementation of the command menu components.
- Introduced the `includeCurrentObjectIfMissing` utility function to streamline the inclusion of the current object in object pickers, enhancing code clarity and maintainability.
- Refactored the rendering logic for object menu items to utilize a dedicated `renderObjectMenuItem` function, improving readability and reducing redundancy.
- Updated sorting logic for object arrays to ensure consistent ordering based on `labelPlural`.
These changes contribute to a more efficient and organized implementation of the command menu components.
- Introduced the `getAbsoluteUrl` utility function to streamline URL normalization when updating links.
- Simplified the logic for setting the link URL by directly using `selectedItem.link`, improving code clarity and maintainability.
- Removed redundant variables to enhance the overall structure of the component.
These changes contribute to a more efficient and readable implementation of the command menu components.
- Changed the `disabled` prop in `CommandMenuEditOwnerSection` to a shorthand boolean syntax for improved clarity.
- Removed the redundant `true` value assignment, enhancing code readability and maintainability.
These changes contribute to a cleaner implementation of the command menu components.
- Updated `CommandMenuEditLinkItemView` and `CommandMenuEditObjectViewBase` components to simplify prop usage by removing redundant `true` values for boolean props.
- Enhanced code readability and maintainability by streamlining the component structure.
These changes contribute to a cleaner and more efficient command menu implementation.
- Introduced utility functions `getDescendantFolderIds` and `excludeCurrentFolder` to enhance the logic for managing folder selections.
- Simplified the logic for determining which folders to display based on the current selection and search query, improving code clarity and maintainability.
- Updated the rendering logic to ensure consistent handling of folder options, enhancing the user experience within the command menu.
These changes streamline the folder selection process, improving the overall functionality and readability of the command menu component.
- Deleted the `CommandMenuEditFolderItemView` component to streamline the codebase.
- Integrated its functionality directly into `CommandMenuNavigationMenuItemEditPage`, enhancing component cohesion and reducing dependencies.
These changes improve maintainability by consolidating related logic within a single component, simplifying the overall structure of the command menu.
- Deleted the `CommandMenuEditDefaultView` component to streamline the codebase.
- Integrated its functionality directly into `CommandMenuNavigationMenuItemEditPage`, enhancing component cohesion and reducing dependencies.
These changes improve maintainability by consolidating related logic within a single component, simplifying the overall structure of the command menu.
- Deleted the `CommandMenuSharedStyles` file to streamline the codebase.
- Moved the styled components `StyledCommandMenuPlaceholder` and `StyledCommandMenuPageContainer` directly into `CommandMenuNavigationMenuItemEditPage`, enhancing component encapsulation and reducing dependencies.
These changes improve maintainability by consolidating styles within the relevant component, simplifying the overall structure of the command menu.
- Replaced the `CommandMenuNavigationMenuItemIcon` component with a direct implementation of `StyledNavigationMenuItemIconContainer` in `CommandMenuFolderLinkInfo`, enhancing code clarity and reducing component complexity.
- Removed the now-unnecessary `CommandMenuNavigationMenuItemIcon` file to streamline the codebase.
These changes improve the maintainability of the command menu components by simplifying the icon rendering logic.
- Introduced `CommandMenuFolderLinkInfo` to consolidate folder and link item handling within the command menu, improving code organization and reducing redundancy.
- Removed the deprecated `CommandMenuLinkInfo` component to streamline the codebase.
- Updated `CommandMenuPageInfo` to utilize the new `CommandMenuFolderLinkInfo` for rendering, enhancing maintainability and clarity.
These changes enhance the command menu's functionality by simplifying the component structure and improving item management.
- Renamed `selectedId` to `selectedNavigationMenuItemInEditMode` for better understanding of its purpose.
- Updated the logic for retrieving the selected link to enhance readability and maintainability.
- Ensured consistent return statements for null checks, improving overall code structure.
These changes streamline the handling of navigation items within the command menu, enhancing code clarity and functionality.
- Updated `CommandMenuFolderInfo` to improve variable naming, changing `selectedId` to `selectedNavigationMenuItemInEditMode` for clarity.
- Simplified conditional checks in `CommandMenuPageInfo` for rendering folder and link components, enhancing code readability.
- Ensured consistent return statements for null checks, improving overall code structure.
These changes enhance the maintainability and clarity of the command menu components, streamlining the logic for item handling.
- Updated `CommandMenuFolderInfo` and `CommandMenuLinkInfo` to utilize direct IDs for selected items, enhancing clarity and maintainability.
- Simplified the logic for retrieving selected folder and link items by using a unified `selectedId` variable.
- Refactored save functions to improve naming consistency and ensure default values are correctly applied.
These changes streamline the command menu components, improving overall code readability and functionality.
- Replaced the deprecated `useFlattenedWorkspaceSectionItemsForLookup` hook with `useWorkspaceSectionItems` across multiple components, including `CommandMenuFolderInfo`, `CommandMenuLinkInfo`, and `CommandMenuPageInfo`, to streamline item retrieval.
- Introduced `getNavigationMenuItemType` utility to enhance clarity in determining item types, improving the readability of conditional checks.
- Updated related components and hooks to ensure consistency with the new item handling approach, enhancing maintainability and code structure.
These changes simplify the navigation menu item management, improving overall code clarity and functionality.
- Add FILES field on attachment
- Adapt Attachment logic in front to use new resolver/controller
- Update files-field logic to infer applicationId from fieldMetadataId +
ask for fieldMetadataId in upload resolver
- Design update
To do in next PR :
- Adapt activity files logic
## Summary
Updates the avatar URLs for seeded founders to use properly named images
instead of generic numbered placeholder images.
## Changes
Updates `prefill-people.ts` to reference new image paths in
`twentyhq/placeholder-images/founders/`:
| Person | Company | New Image Path |
|--------|---------|----------------|
| Brian Chesky | Airbnb | `founders/brian-chesky.jpg` |
| Dario Amodei | Anthropic | `founders/dario-amodei.jpg` |
| Patrick Collison | Stripe | `founders/patrick-collison.jpg` |
| Dylan Field | Figma | `founders/dylan-field.jpg` |
| Ivan Zhao | Notion | `founders/ivan-zhao.jpg` |
## Related
Requires a corresponding PR on `twentyhq/placeholder-images` to add the
actual founder headshot images to the `founders/` directory.
## Why
The previous placeholder images were generic numbered images that didn't
represent the actual people. This creates a single source of truth for
founder images that all workspaces can reference.
We generate Field widgets for relations on-the-fly, when a record page
layout is first requested by the user. When the user changed their data
model and then returned to a record page layout, the Field widgets
weren't updated. **This PR ensures Field widgets are recomputed when
relations change.**
Future subjects:
- Now that we will fetch the configuration from the backend and start
storing updates, we will have to think about how we deal with these
generated relation Field widgets.
## Before
https://github.com/user-attachments/assets/99d53b19-b231-435f-b14f-4473ba269ad2
## After
https://github.com/user-attachments/assets/357d956d-8b3b-448c-a983-82569a7dd0a0
---------
Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
Issue 1: no info to debug cron trigger. Stop catching exception + using
logs instead of throwing for now
Issue 2: sentry often send timeouts errors for workflow crons. Probably
not real ones, it sends it if the job takes more than 5 minutes to run.
To fix, on each workflow cron we do:
- loop over active workspaces
- perform a query check that workspace is relevant, using count for
performances
- send a job if relevant
## Summary
Three fixes in this PR:
### 1. Migration commands now set `isActive = true` and use generic
'Target' label
When converting relation fields to `MORPH_RELATION` type, the migration
commands now:
- Set `isActive = true` to prevent inactive fields from being selected
as the representative morph field
- Update all field labels to generic **'Target'** instead of keeping
individual labels like 'Company', 'Person'
This ensures the UI shows a coherent label ('Target') alongside 'X
Objects' for the type.
**Files changed:**
- `1-17-migrate-note-target-to-morph-relations.command.ts`
- `1-17-migrate-task-target-to-morph-relations.command.ts`
### 2. Added system fields/relations toggles in Settings
The fields and relations tables in Settings > Data Model were filtering
out system fields with no way to view them. Added a "System fields" /
"System relations" toggle (visible in advanced mode) to allow viewing
these fields.
**Files changed:**
- `SettingsObjectFieldTable.tsx`
- `SettingsObjectRelationsTable.tsx`
This matches the existing behavior on the Objects table which already
has a "System objects" toggle.
- Removed the `getWorkspaceSectionItemId` utility function and updated components to directly access item IDs, simplifying the logic for identifying navigation menu items.
- Adjusted various components, including `CommandMenuFolderInfo`, `CommandMenuLinkInfo`, and `CommandMenuNavigationMenuItemEditPage`, to utilize the new ID structure, enhancing code clarity and maintainability.
- Updated related hooks and types to reflect the changes in item ID handling, ensuring consistency across the navigation menu item management.
These changes streamline the navigation item handling process, improving the overall structure and readability of the codebase.
- Renamed `selectedFolder` to `selectedNavItem` for clarity in distinguishing between folder and link types.
- Updated conditional checks to use the new `selectedNavItem` variable, enhancing readability and maintainability of the component's logic.
These changes streamline the handling of navigation items within the command menu, improving code clarity and functionality.
## Summary
The lockfile had a stale reference to `dist/cli/index.cjs` but the
twenty CLI bin was changed to `dist/cli.cjs`.
This was causing CI to fail with:
```
YN0028: - twenty: dist/cli/index.cjs
YN0028: + twenty: dist/cli.cjs
YN0028: The lockfile would have been modified by this install, which is explicitly forbidden.
```
This PR updates the lockfile to match the new path.
- Added `applicationId` prop to `CommandMenuEditFolderItemView`, `CommandMenuEditLinkItemView`, and `CommandMenuEditOwnerSection` for improved context handling.
- Updated rendering logic in `CommandMenuNavigationMenuItemEditPage` to pass the correct `applicationId` to folder items.
- Removed unused selectable item IDs in `CommandMenuEditFolderItemView` and `CommandMenuEditLinkItemView` for cleaner code.
These changes improve the modularity and functionality of command menu components, enhancing the user experience by providing relevant application context.
This bug was cause by a combination of an expired token stored inside
SSE client on the front end, and a server restart or a cache flush, we
ended up with an SSE client that tries to reconnect infinitely with an
expired token.
The fix is to improve the connection retry handler in the frontend to
detect an expired token and recreate an SSE client.
## Overview
Fixes the `unknown fields opportunityId in objectMetadataItem
noteTarget` error when creating notes/tasks from record pages (like the
Notes/Tasks tab on an opportunity).
## Root Cause
The `useOpenCreateActivityDrawer` hook was not handling `MORPH_RELATION`
field types:
1. Code only checked `field.relation` (which is populated for `RELATION`
type fields)
2. When `field.relation` was undefined (because the field is
`MORPH_RELATION`), it fell back to the old naming convention (e.g.,
`opportunityId`)
3. But after the morph migration, the columns are named differently
(e.g., `targetOpportunityId`)
This caused the error on both new workspaces (which are created with
morph relations from the start) and existing workspaces that ran the
migration.
## Solution
Use the existing `findTargetFieldInfo()` utility which properly handles
both `RELATION` and `MORPH_RELATION` field types by:
- Checking `morphRelations` for morph fields and computing the correct
field name
- Checking `relation` for regular relation fields
- Returning the correct `joinColumnName` for each case
This utility is already used elsewhere in the codebase (e.g., in
junction field handling) and is the cleanest solution.
- Introduced `enableOnFormTags` option in the `useCommandMenuHotKeys` hook to control hotkey activation within form elements, enhancing flexibility in user interactions.
- Updated the options for focused element hotkeys to include the same `enableOnFormTags` setting, ensuring consistent behavior across different contexts.
These changes improve the usability of hotkeys in the command menu, allowing for better integration with form elements.
- Introduced the `useFilteredPickerItems` hook to streamline the filtering of items based on a search query, enhancing reusability across command menu components.
- Deleted several outdated components, including `CommandMenuEditObjectPickerSubView`, `CommandMenuEditObjectPickerSystemSubView`, and `CommandMenuEditViewItemView`, to simplify the codebase.
- Added `CommandMenuEditObjectViewBase`, `CommandMenuObjectPickerSubView`, and `CommandMenuSystemObjectPickerSubView` to improve the organization and functionality of object selection within the command menu.
- Updated `CommandMenuNavigationMenuItemEditPage` and `CommandMenuNewSidebarItemPage` to utilize the new picker components, enhancing the user experience and maintaining consistency.
These changes improve the structure and maintainability of the command menu, providing a more efficient interface for object selection and management.
- Introduced a new helper function, `swapPositionsInDraft`, to streamline the logic for swapping positions of navigation menu items in the draft state.
- Replaced inline position swapping logic with the new helper function for better readability and maintainability.
These changes enhance the clarity of the `useNavigationMenuItemMoveRemove` hook, making it easier to manage item positions within the navigation menu.
- Replaced the custom back button with the `SidePanelSubPageNavigationHeader` component for improved navigation consistency.
- Removed unused styled components related to the back button, streamlining the code and enhancing maintainability.
These changes enhance the user experience by providing a more cohesive navigation interface within the command menu.
- Removed the `preventDefault` option from the `useMouseDownNavigation` hook to streamline the API and ensure consistent behavior.
- Updated event handling to always prevent default actions for regular clicks, enhancing navigation reliability.
These changes improve the clarity and usability of the `useMouseDownNavigation` hook, ensuring a more predictable user experience.
- Updated `CommandMenuEditFolderPickerSubView` to include `currentFolderId` for improved folder selection logic, ensuring that the current folder is excluded from the options when applicable.
- Replaced `IconFolder` with `IconFolderPlus` in both `CommandMenuEditFolderPickerSubView` and `CommandMenuEditOrganizeActions` for a more intuitive icon representation.
- Refactored folder filtering logic to enhance clarity and maintainability, improving the user experience when selecting folders.
These changes streamline the folder selection process and enhance the visual consistency of the command menu components.
- Replaced hardcoded owner items in `CommandMenuEditFolderItemView` and `CommandMenuEditLinkItemView` with a new `CommandMenuEditOwnerSection` component for better modularity and reusability.
- Cleaned up imports and removed unused code to enhance readability and maintainability across multiple command menu components.
- Streamlined the rendering logic in `CommandMenuEditObjectPickerSubView` and `CommandMenuEditViewPickerSubView` for improved clarity.
These changes improve the structure of the command menu components, making them more maintainable and enhancing the overall user experience.
- Replaced instances of `useWorkspaceSectionItems` with `useFlattenedWorkspaceSectionItemsForLookup` in multiple command menu components, including `CommandMenuFolderInfo`, `CommandMenuLinkInfo`, and `CommandMenuPageInfo`, to streamline data retrieval.
- Enhanced drag-and-drop functionality in `CommandMenuItemWithAddToNavigationDrag` by introducing a new constant for folder drag types and updating event handling.
- Added `NavigationDropTargetContext` to manage drag-and-drop states, improving the user experience when interacting with navigation items.
- Introduced `NavigationItemDropTarget` component to facilitate drop target behavior for navigation items, enhancing the overall drag-and-drop interface.
These changes improve code maintainability and enhance the user experience by providing a more efficient and intuitive command menu interface.
- Moved the Apollo import statement to the top of the file for consistency.
- Removed duplicate type definition for `NavigationMenuItemFieldsFragment`, ensuring clarity and reducing redundancy.
- Ensured proper formatting and consistency in type definitions throughout the file.
These changes enhance code readability and maintainability in the generated GraphQL metadata.
- Deleted the `CommandMenuEditFolderRenameSubView` component from the command menu, streamlining the editing interface.
- Updated `CommandMenuNavigationMenuItemEditPage` to remove references to the deleted component, enhancing code clarity and maintainability.
These changes simplify the command menu structure and improve overall code organization.
- Updated `CommandMenuNavigationMenuItemIcon` to utilize `StyledNavigationMenuItemIconContainer` for consistent icon styling based on theme colors.
- Introduced `CommandMenuSharedStyles` for shared styles across command menu components, enhancing maintainability and reducing redundancy.
- Refactored `CommandMenuNavigationMenuItemEditPage` and other components to leverage new shared styles, improving code clarity and user interface consistency.
These changes enhance the user experience by providing a more cohesive and visually appealing command menu interface.
- Introduced several new components including `CommandMenuEditDefaultView`, `CommandMenuEditFolderItemView`, `CommandMenuEditFolderPickerSubView`, `CommandMenuEditFolderRenameSubView`, `CommandMenuEditLinkItemView`, `CommandMenuEditObjectItemView`, `CommandMenuEditObjectPickerSubView`, `CommandMenuEditObjectPickerSystemSubView`, `CommandMenuEditViewItemView`, and `CommandMenuEditViewPickerSubView` to improve the editing capabilities within the command menu.
- Refactored `CommandMenuNavigationMenuItemEditPage` to utilize these new components, enhancing the organization and functionality of the editing interface.
- Streamlined the search functionality and improved user feedback through better handling of selectable items and no results scenarios.
These changes significantly enhance the user experience by providing a more intuitive and flexible interface for editing navigation items within the command menu.
- Updated the `CommandMenuNavigationMenuItemEditPage` to enhance the search functionality by introducing clearer handling of empty search results.
- Refactored the logic for generating selectable item IDs to improve clarity and maintainability.
- Added conditional rendering for no results text, providing better user feedback when no system objects are found.
These changes enhance the user experience by making the search interface more intuitive and responsive to user input.
- Added `CommandMenuNewSidebarItemMainMenu` and `CommandMenuNewSidebarItemRecordSubView` components to enhance the command menu's sidebar item functionality.
- Refactored `CommandMenuNewSidebarItemPage` to utilize the new components, streamlining the process of adding new items and managing records.
- Optimized existing logic by removing unnecessary hooks and simplifying state management, improving code clarity and maintainability.
These changes significantly enhance the user experience by providing a more intuitive interface for adding and managing sidebar items within the command menu.
- Introduced several new components including `CommandMenuEditOrganizeActions`, `CommandMenuObjectMenuItem`, `CommandMenuSelectObjectForEditMenuItem`, `CommandMenuSelectObjectForViewMenuItem`, and hooks for managing navigation menu object metadata from drafts.
- Refactored `CommandMenuNavigationMenuItemEditPage` and `CommandMenuNewSidebarItemPage` to utilize these new components, improving the organization and functionality of the command menu.
- Removed redundant code and optimized existing logic for better maintainability and clarity.
These changes significantly enhance the user experience by providing a more intuitive and flexible interface for managing navigation items within the command menu.
- Updated `CommandMenuSubViewWithSearch` to accept optional `searchInputProps` and made `children` prop optional for improved flexibility.
- Refactored `CommandMenuNavigationMenuItemEditPage` to utilize `CommandMenuSubViewWithSearch`, streamlining the search functionality and enhancing the user interface.
- Removed redundant styled components from `CommandMenuNavigationMenuItemEditPage`, improving code clarity and maintainability.
These changes improve the overall user experience by providing a more consistent and flexible search interface within the command menu.
- Introduced `CommandMenuNavigationMenuItemIcon` component to standardize icon rendering for folder and link items in the command menu.
- Updated `CommandMenuFolderInfo` and `CommandMenuLinkInfo` to utilize the new icon component, enhancing code reusability and maintainability.
- Added `CommandMenuSubViewWithSearch` component to streamline search functionality within the command menu, replacing custom search implementations in `CommandMenuNewSidebarItemPage`.
- Refactored utility functions for workspace section item ID retrieval, improving clarity and reducing redundancy.
These changes enhance the user experience by providing a consistent interface for icons and improving search capabilities within the command menu.
- Introduced `CommandMenuItemWithAddToNavigationDrag` component to facilitate drag-and-drop interactions within the command menu.
- Updated `CommandMenuNewSidebarItemPage` to utilize the new component, streamlining the addition of navigation items.
- Refactored existing drag-and-drop logic into the new component, improving code organization and maintainability.
- Enhanced visual feedback during drag operations by integrating drag preview functionality.
These changes significantly improve the user experience by making it easier to add items to the navigation menu through intuitive drag-and-drop actions.
- Introduced new components for drag-and-drop functionality within the navigation menu, including `AddToNavigationDragHandle`, `NavigationItemDropTarget`, and `NavigationSidebarNativeDropZone`.
- Updated existing components to support drag-and-drop interactions, allowing users to easily rearrange navigation items and add new items through drag events.
- Enhanced the `useAddToNavigationMenuDraft` hook to manage the addition of items at specific positions based on drag-and-drop actions.
- Improved the user experience by providing visual feedback during drag operations and ensuring seamless integration with existing navigation menu functionalities.
These changes significantly enhance the usability of the navigation menu, making it more intuitive for users to manage their navigation items.
- Introduced `CommandMenuLinkInfo` component to manage link-specific interactions within the command menu.
- Updated `CommandMenuPageInfo` to conditionally render `CommandMenuLinkInfo` when editing a navigation menu item of type link.
- Enhanced navigation menu item editing capabilities by allowing users to manage links directly within the command menu.
These changes improve the user experience by providing a dedicated interface for link management, streamlining navigation and editing processes.
- Introduced a new optional `link` field in the `NavigationMenuItem` type, allowing for external links to be associated with navigation items.
- Updated the `CreateNavigationMenuItemInput`, `UpdateNavigationMenuItemInput`, and related DTOs to include the `link` field.
- Modified the database schema with a migration to add the `link` column to the `navigationMenuItem` table.
- Enhanced validation logic to accommodate the new `link` field, ensuring proper handling during navigation menu item creation and updates.
These changes improve the flexibility of navigation menu items by enabling the inclusion of external links, enhancing user navigation capabilities.
# Refactor workflow–logic function interaction
## Why
Workflow code steps and standalone logic functions shared the same build
layer and DB layer, which blurred two use cases: code steps belong to a
workflow version; standalone functions are deployable units. That made
workflow code steps harder to own and evolve.
## Goal
Treat code steps as **workflow-owned**: build and run them in workflow
context, and expose workflow-scoped APIs so the editor can load, test,
and save code step source without going through the generic
logic-function layer.
- Introduced `CommandMenuFolderInfo` component to manage folder-specific interactions within the command menu.
- Updated `CommandMenuPageInfo` to conditionally render `CommandMenuFolderInfo` when editing a navigation menu item in folder mode.
- Enhanced folder management capabilities by adding folder creation and editing functionalities in the command menu.
These changes improve the user experience by providing a dedicated interface for folder management within the command menu, streamlining navigation and editing processes.
- Added `useCommandMenu` hook to manage command menu interactions.
- Updated save draft logic to close the command menu upon successful save, enhancing user experience during navigation menu edits.
This change improves the responsiveness of the navigation menu editing process by ensuring the command menu closes automatically after saving changes.
- Added a `secondaryLabel` prop to `NavigationDrawerItemForObjectMetadataItem` component.
- The `secondaryLabel` is conditionally set based on whether the item is a record or a view with a custom name, improving the clarity of displayed metadata.
This change enhances the user interface by providing additional context for object metadata items in the navigation drawer.
Previous code in `MESSAGING_GMAIL_DEFAULT_NOT_SYNCED_LABELS` was
problematic as we grouped `category` labels along with `system folders`
labels together.
This fixes partially missing emails issue by splitting it and not
applying category exclusion when querying for a singular custom label.
Also removes old approach of getting message label_id's association from
additional network call overhead to local utility
`filterGmailMessagesByFolderPolicy`
## Context
Fix TypeError: Cannot read properties of undefined (reading
'coreDataSource') when updating records with RLS predicates enabled
## Implementation
Use lazy initialization for FilesFieldSync and RelationNestedQueries in
workspace query builder
## Technical details
When Row-Level Security (RLS) predicates are applied during an update
operation, TypeORM internally creates sub-query builders to evaluate
Brackets in WHERE clauses. TypeORM's createQueryBuilder() method calls
new this.constructor(connection, queryRunner) with only 2 arguments, but
WorkspaceUpdateQueryBuilder expects 6 arguments including
internalContext.
This caused FilesFieldSync to be instantiated with undefined context,
resulting in the error when accessing internalContext.coreDataSource.
Converting filesFieldSync and relationNestedQueries from eager
initialization in the constructor to lazy getters. This way:
TypeORM internal sub-query builders work fine (they never access these
dependencies)
Real query builders create dependencies on-demand when execute() runs
---------
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Weiko <Weiko@users.noreply.github.com>
## Context
Previously, if for example a Person had a companyId but the company
relation was null due to RLS, the company column appeared empty. Now it
displays ForbiddenFieldDisplay to indicate the relation exists but is
inaccessible.
When RLS restricts access to a related record, the frontend now shows a
"Not shared" indicator (with lock icon) instead of an empty field.
<img width="1275" height="399" alt="Screenshot 2026-02-04 at 15 37 53"
src="https://github.com/user-attachments/assets/870306f8-f811-4dc0-b23b-a33e89043a37"
/>
## Summary
Replace NestJS `ConfigService` with `TwentyConfigService` for
`ENTERPRISE_KEY` access in row-level permission services.
## Changes
- `row-level-permission-predicate.service.ts`: Updated to use
`TwentyConfigService`
- `row-level-permission-predicate-group.service.ts`: Updated to use
`TwentyConfigService`
## Why
`TwentyConfigService` also pulls config values from the database, not
just environment variables. This ensures consistent config access across
the codebase.
- Introduced `CommandMenuSelectObjectForViewEditMenuItem` component to facilitate object selection for view editing.
- Enhanced state management in `CommandMenuNavigationMenuItemEditPage` to track selected object metadata for view editing.
- Updated logic to filter and sort objects based on their association with views, improving the user experience during object selection.
- Adjusted rendering logic to differentiate between object and view editing modes, ensuring clarity in user interactions.
These changes enhance the command menu's functionality, providing users with a more intuitive experience when managing object views.
# Introduction
Removing:
- `from` property from actions definition, as it's a legitimate source
of truth. The stored comparison might have been compromised since action
generation. If from is needed it should be computed from the optimistic
cache at runner lvl
- Removed the `FlatEntityPropertyUpdates` Array complexity in favor of
From
```ts
export type PropertyUpdate<T, P extends keyof T> = {
property: P;
} & FromTo<T[P]>;
```
To
```ts
export type FlatEntityUpdate<T extends AllMetadataName> = Partial<
Pick<
MetadataFlatEntity<T>,
Extract<FlatEntityPropertiesToCompare<T>, keyof MetadataFlatEntity<T>>
>
>;
```
## New interactions
From
```ts
const positionUpdate = findFlatEntityPropertyUpdate({
flatEntityUpdates,
property: 'position',
});
if (
isDefined(positionUpdate) &&
(!Number.isInteger(positionUpdate.to) || positionUpdate.to < 0)
) {
const toFlatNavigationMenuItem = {
...fromFlatNavigationMenuItem,
...fromFlatEntityPropertiesUpdatesToPartialFlatEntity({
updates: flatEntityUpdates,
}),
};
```
To
```ts
const positionUpdate = flatEntityUpdate.position;
if (
isDefined(positionUpdate) &&
(!Number.isInteger(positionUpdate) || positionUpdate < 0)
) {
const toFlatNavigationMenuItem = {
...fromFlatNavigationMenuItem,
...flatEntityUpdate,
};
```
## `SanitizeFlatEntityUpdate`
Enforcing the `flatEntityUpdate` to only contains comparable properties
per flat entity by striping out all unexpected keys
In the future we will also move the whole validation at runner lvl at
some point
```ts
export const sanitizeFlatEntityUpdate = <T extends AllMetadataName>({
flatEntityUpdate,
metadataName,
}: {
flatEntityUpdate: FlatEntityUpdate<T>;
metadataName: T;
}): FlatEntityUpdate<T> => {
const { propertiesToCompare } =
ALL_FLAT_ENTITY_PROPERTIES_TO_COMPARE_AND_STRINGIFY[metadataName];
const initialAccumulator: FlatEntityUpdate<T> = {};
return propertiesToCompare.reduce((accumulator, property) => {
const updatedValue =
flatEntityUpdate[property as MetadataFlatEntityComparableProperties<T>];
if (updatedValue === undefined) {
return accumulator;
}
return {
...accumulator,
[property]: updatedValue,
};
}, initialAccumulator);
};
```
- Updated the `WorkspaceNavigationMenuItemsFolder` component to conditionally render secondary labels based on the view key, improving clarity in navigation item representation.
- Simplified the logic in `sortNavigationMenuItems` to ensure consistent handling of object names, enhancing the accuracy of displayed labels.
These changes enhance the user experience by providing clearer navigation item details and improving the overall sorting logic.
- Updated the `CommandMenuNavigationMenuItemEditPage` to handle empty folder states more gracefully, displaying a custom message when no folders are available.
- Refactored the logic for generating selectable item IDs to include a fallback for empty states.
- Improved the sorting logic in `sortNavigationMenuItems` to correctly handle index views and associated metadata, ensuring accurate display of labels and icons.
- Adjusted test cases to reflect changes in object naming conventions and ensure consistency in expected outcomes.
These changes improve the user experience by providing clearer feedback and more accurate representations of navigation items in the command menu.
- Consolidated multiple hooks for adding items to the navigation menu draft into a single `useAddToNavigationMenuDraft` hook, streamlining the process for adding objects, views, and records.
- Removed outdated hooks `useAddObjectToNavigationMenuDraft`, `useAddViewToNavigationMenuDraft`, and refactored related components to utilize the new consolidated hook.
- Introduced `useUpdateNavigationMenuItemsDraft` hook to manage updates to navigation menu items in draft state, enhancing item editing capabilities.
- Enhanced the `CommandMenuNavigationMenuItemEditPage` with improved state management and additional functionality for object selection and navigation item manipulation.
- Updated the `useNavigationMenuItemMoveRemove` hook to include a new `moveToFolder` function, allowing for better organization of navigation items.
These changes improve the overall efficiency and usability of the command menu, providing users with a more cohesive experience when managing navigation items.
- Updated the back navigation logic in the `CommandMenuNewSidebarItemPage` to handle system object selections more effectively.
- Introduced a check for system object metadata to set the appropriate navigation option when returning to the view object list.
- Improved user experience by ensuring the correct state is maintained during navigation actions.
These changes enhance the functionality of the command menu, providing users with a more intuitive navigation experience when dealing with system objects.
- Added a new `noResultsText` prop to the `CommandMenuList` component, allowing for customizable no results messages.
- Updated the `CommandMenuNewSidebarItemPage` to utilize the new prop, providing context-specific messages based on user input.
- Refactored the logic for displaying results to improve clarity and user experience when no views are found.
These changes enhance the flexibility of the command menu, improving user feedback during navigation item searches.
- Integrated functionality for adding views to the navigation menu draft within the `CommandMenuNewSidebarItemPage`.
- Introduced a new hook, `useAddViewToNavigationMenuDraft`, to manage view additions effectively.
- Updated state management to accommodate view selection and search inputs, enhancing user interaction.
- Refactored related components and utilities to support view handling, improving overall command menu functionality.
These changes enhance the command menu's capabilities, allowing users to manage views alongside other navigation items seamlessly.
- An app will declare its `twenty-sdk` version in the manifest.
- `twenty-sdk` will be served by a CDN to be imported in front component
host.
- When we render the host we will load twenty-ui through the correct
version of the sdk
We will proceed this way because twenty-ui is not mature enough yet to
be deployed as its own package. So instead, since the sdk is already
deployed as its own package, we will serve the ui with it. It allows us
to have versioning to handle breaking changes in twenty-ui.
## Problem
The `deleteFromLocalCache` method was only setting `lastHashCheckedAt=0`
instead of actually deleting the cache entry. This caused old versions
to accumulate in the local cache when `invalidateAndRecompute` was
called.
### Flow that causes the leak:
1. `invalidateAndRecompute()` is called
2. `flush()` → `deleteFromLocalCache()` — **only sets
`lastHashCheckedAt=0`, keeps old data**
3. `recomputeDataFromProvider()` → `setInLocalCache()` — **adds new
version with new hash**
4. `cleanupStaleVersions()` — **never called** (only triggered from
`getFromLocalCache` path)
Result: Each `invalidateAndRecompute` call adds a new version to
`entry.versions` without removing the old one.
### Impact
For migration commands (like
`1-17-migrate-attachment-to-morph-relations`) that process thousands of
workspaces, this caused significant memory growth:
- Each workspace calls `getOrRecompute` (version 1)
- Then calls `invalidateAndRecompute` (version 2 added, version 1 stays)
- Memory accumulates as the command processes more workspaces
## Solution
Actually delete the local cache entry in `deleteFromLocalCache`, so
`recomputeDataFromProvider` starts fresh.
```typescript
// Before (memory leak)
if (isDefined(entry)) {
entry.lastHashCheckedAt = 0;
}
// After (proper cleanup)
this.localCache.delete(localKey);
```
---------
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
When `IS_WORKSPACE_CREATION_LIMITED_TO_SERVER_ADMINS` is true, anonymous
users could still create workspaces because
`checkWorkspaceCreationIsAllowedOrThrow` checked per-user workspace
count (always 0 for new users) instead of system-wide workspace count.
The bootstrap bypass now only applies when no workspaces exist in the
entire system. Also adds the same check in `signUpOnNewWorkspace` to
guard the `signInUp` code path.
Fixes#17631
Generated with [Claude Code](https://claude.ai/claude-code)
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
- Added support for system objects in the `CommandMenuNewSidebarItemPage`, allowing users to filter and select system-related items.
- Introduced new state management for system object search input and updated the back navigation logic for improved user experience.
- Refactored object metadata filtering to include system objects, enhancing the overall functionality of the command menu.
- Improved search functionality by implementing a dedicated search input for objects, streamlining the selection process.
These changes enhance the command menu's capabilities, providing users with more comprehensive options for managing navigation items.
- Eliminated the assignment of targetObjectMetadataId to undefined when viewId is defined, streamlining the input handling logic.
- This change improves the clarity and efficiency of the hook's functionality, ensuring only relevant data is processed.
## Remove logic function layer
Package.json and yarn.lock are now on the application entity, so the
logic function layer is no longer used except as a legacy source for the
1.17 backfill. This PR removes all layer usage outside of that migration
and keeps only the entity for backfill.
### Summary
- **Kept:** `LogicFunctionLayerEntity` and its table, only used by the
1.17 backfill command to read legacy layer data and backfill application
package files.
- **Removed:** All other layer logic: CRUD, cache, resolvers, services,
DTOs, and frontend types. Logic functions now depend only on the
application for package/dependency context.
### Why Dependencies instead of Source for package files
Package.json and yarn.lock are the application’s dependency set and are
stored under the application in **FileFolder.Dependencies**. The build
service and drivers now read them only from Dependencies; nothing is
written to Source for these files.
This PR migrates `noteTarget` and `taskTarget` to morph relations behind
separate feature flags, following the Attachment/TimelineActivity
pattern.
It introduces the `IS_NOTE_TARGET_MIGRATED` and
`IS_TASK_TARGET_MIGRATED` flags, updates standard field metadata and
indexes to use morph relations, and adds two **1.17 workspace
migrations** that:
- rename `noteTarget.*Id` / `taskTarget.*Id` columns to `target*Id`
- convert the corresponding field metadata to `MORPH_RELATION` with a
shared `morphId`
On the frontend, note/task target read and write paths switch to
`target*Id` when the respective flag is enabled. Deleted targets are
filtered on reload to prevent reappearing relations.
Replace manual HTTP batch implementation with
`@jrmdayn/googleapis-batcher` library (we already use this for fetching
message list)
Initial real testing works fine but do not merge yet needs more
extensive real test runs
# Backfill application package files for custom and standard apps
- Backfill `package.json` / `yarn.lock` (and related fields) for
existing workspaces; new standard/custom apps get default dependency
files.
- Default package files under
`application/constants/default-package-files/`; util with hardcoded
checksums (comment on how to regenerate).
- New **FileFolder.Dependencies** for app dependency files;
**writeFile_v2** accepts optional `queryRunner` for transactional
writes.
Usages:
- **Upgrade command** `upgrade:1-17:backfill-application-package-files`:
standard/custom apps → default files; other apps → from logic function
layer.
- **Workspace creation**: create workspace with
`workspaceCustomApplicationId` first, then create application (enables
same-transaction insert). Migration makes workspace/application/file FKs
deferrable.
- dev seeder
PR #17492 fixed `markAsMessagesListFetchOngoing` to set
`syncStageStartedAt`, but also changed `isSyncStale` to return `false`
for null values. This prevented the stale recovery job from recovering
channels that were already stuck before the fix was deployed.
Changing `isSyncStale` to return `true` for null/undefined allows the
stale job to properly reset stuck channels back to PENDING state.
# Introduction
In preparation of the workspace agnostic builder, we're migrating
`FlatEntityMaps` to be universal identifier oriented and based
As in the builder context there're won't be any ids at all
Please also note that the FlatEntity is a UniversalFlatEntity superset
From
```ts
import { type SyncableFlatEntity } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-from.type';
export type FlatEntityMaps<T extends SyncableFlatEntity> = {
byId: Partial<Record<string, T>>;
idByUniversalIdentifier: Partial<Record<string, string>>;
universalIdentifiersByApplicationId: Partial<Record<string, string[]>>;
};
```
To
```ts
export type FlatEntityMaps<
T extends SyncableFlatEntity | UniversalSyncableFlatEntity,
> = {
byUniversalIdentifier: Partial<Record<string, T>>;
universalIdentifierById: Partial<Record<string, string>>;
universalIdentifiersByApplicationId: Partial<Record<string, string[]>>; // this might make more sense to be migrated to universalIdentifiersByApplicationUniversalIdentifier but it's the main topic of this PR
};
```
## Low level maps tools
Had to refactor find | create | delete | replace | find-many | get-sub
tools ( through mutations and or throw equivalent )
- Introduced the `CommandMenuNewSidebarItemPage` component to facilitate the addition of new items to the navigation menu.
- Updated the `CommandMenuPagesConfig` to include the new sidebar item page, enhancing navigation options.
- Implemented hooks for adding objects and records to the navigation menu draft, improving item management.
- Refactored the `NavigationMenuEditModeBar` to support saving drafts and handling loading states, streamlining the editing process.
These changes enhance the command menu's functionality, providing users with more options for managing navigation items effectively.
## Root Cause
The `DateTimePicker` component lazy-loads `react-datepicker` using
React's `lazy()` with a `Suspense` fallback that shows skeleton loaders:
```typescript
const ReactDatePicker = lazy<ComponentType<DatePickerPropsType>>(() =>
import('react-datepicker').then(...)
);
// In render:
<Suspense fallback={<SkeletonLoader />}>
<ReactDatePicker ... />
</Suspense>
```
On slower CI runners (GitHub Actions vs depot.dev), the lazy load takes
longer, causing tests to timeout while still showing skeletons instead
of the actual date picker content.
## Fix
Increased `findByText` timeout from the default 1000ms to 10000ms for
tests that wait for the date picker to load:
- `DateTimeFieldInput.stories.tsx` - 4 stories fixed
- `InternalDatePicker.stories.tsx` - 2 stories fixed
## Why This Wasn't Flaky Before
depot.dev runners have faster I/O and more consistent performance, so
the lazy load completed quickly. GitHub Actions runners have more
variable performance, causing the load to sometimes exceed the 1000ms
default timeout.
## Summary
Follow-up to #17656 - the `RelationToOneFieldDisplay` performance test
is still failing on GitHub Actions runners.
**Failure:** 0.313ms actual vs 0.3ms threshold
**Fix:** Increased threshold from 0.3ms → 0.4ms to provide more headroom
for runner variability.
- fixedOverflowWidgets fixes the suggestions being overflow outside the
editor
- on focus, stop event propagation to avoid catching spaces as document
level events
## Summary
Fixes the dark mode error chip background color issue by changing
`background.danger` from `red12` to `red3` in the dark theme constants.
Fixes#17657
Generated with [Claude Code](https://claude.ai/code)
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Thomas des Francs <Bonapara@users.noreply.github.com>
## Summary
After migrating from depot.dev runners to GitHub Actions runners, three
performance tests are failing due to slower single-threaded performance
on the new infrastructure (even with 16 cores).
## Problem
The performance tests use React's `<Profiler>` API to measure component
render times. depot.dev runners have better single-core performance and
lower memory latency compared to GitHub Actions runners, even larger
ones.
Failed tests:
| Test | Threshold | Actual | Overage |
|------|-----------|--------|---------|
| `DateTimeFieldDisplay` | 0.2ms | 0.281ms | +41% |
| `ChipFieldDisplay` | 0.2ms | 0.222ms | +11% |
| `RelationToOneFieldDisplay` | 0.22ms | 0.237ms | +8% |
## Solution
Increased thresholds to account for GitHub Actions runner performance:
- `DateTimeFieldDisplay`: 0.2ms → 0.35ms
- `ChipFieldDisplay`: 0.2ms → 0.3ms
- `RelationToOneFieldDisplay`: 0.22ms → 0.3ms
## Future Considerations
For a more robust long-term solution, we could implement baseline
comparison that:
1. Saves performance results on main branch builds
2. Compares PR results against baseline with tolerance (±25%)
3. Self-calibrates to actual runner performance
This would catch real regressions without being sensitive to
infrastructure differences.
# Introduction
Following https://github.com/twentyhq/twenty/pull/17632 and
https://github.com/twentyhq/twenty/pull/17572
This PR deprecates the agent, skill, field metadata and role
`standardId` in favor of the `universalIdentifier` usage
## Note
- Removed previous standard ids declaration modules
- Twenty-sdk now re-exports the `STANDARD_OBJECTS` universalIdentifier
hashmap constant
- deleted some sync-metadata deadcode too ( mainly types )
- Introduced the ability to move navigation menu items up and down within the command menu, improving item organization.
- Added a remove option to delete selected navigation menu items, streamlining item management.
- Refactored the `CommandMenuNavigationMenuItemEditPage` component to utilize new hooks for item manipulation and state management.
- Updated related components and hooks to ensure consistent handling of navigation menu items.
These changes enhance the user experience by providing more control over navigation menu item arrangements and management.
- Adjusted the width calculation in the `NavigationDrawerItem` component to account for additional spacing when right options are present.
- Improved the responsiveness of the navigation drawer by refining the width logic for both expanded and collapsed states.
These changes enhance the visual consistency and usability of the navigation drawer items.
- Replaced the `selectedWorkspaceObjectMetadataItemIdInEditModeState` with `selectedNavigationMenuItemInEditModeState` to streamline state management for navigation menu items.
- Updated components to utilize the new state, enhancing clarity and consistency across the navigation menu.
- Refactored hooks and component logic to improve handling of navigation menu item selection and editing, ensuring a more intuitive user experience.
- Introduced new utility functions and styled components to support the updated navigation structure.
These changes enhance the overall functionality and maintainability of the navigation menu system.
- Updated the navigation menu item components to utilize user-specific navigation items, enhancing the organization of workspace-related views.
- Introduced a new `WorkspaceNavigationMenuItemsFolder` component to manage folder items within the workspace navigation.
- Refactored hooks to separate workspace and user navigation items, improving data handling and clarity.
- Added utility functions to identify navigation menu item folders, streamlining the integration of folder items in the navigation structure.
These changes enhance the user experience by providing a more structured and intuitive navigation menu for workspace items.
- Introduced a new 'workflowsFolder' item in the standard navigation menu, enhancing organization of workflow-related views.
- Added new entries for 'workflowsFolderAllWorkflows', 'workflowsFolderAllWorkflowRuns', and 'workflowsFolderAllWorkflowVersions' to improve navigation and access to workflow data.
- Created utility functions for generating flat metadata for folder items, streamlining the integration of new navigation items.
- Updated existing view and view field utilities to include support for workflow versions, ensuring comprehensive coverage in the navigation structure.
These changes enhance the user experience by providing a clearer and more structured navigation menu for workflows and their associated views.
- Removed unused imports and state management related to views and context store, streamlining the component.
- Simplified the rendering logic by eliminating the collapsible container and directly rendering the `NavigationDrawerItem`.
- This refactor enhances readability and maintainability of the code while preserving existing functionality.
- Added support for handling active item clicks in the `WorkspaceNavigationMenuItems` component, allowing users to interact with items even when not in edit mode.
- Introduced a new styled container for right icons to improve layout and spacing.
- Updated `NavigationDrawerItemForObjectMetadataItem` and `NavigationDrawerSectionForObjectMetadataItems` components to accommodate the new click handling logic, enhancing user experience and interaction consistency.
These changes improve the functionality and usability of the navigation menu, making it more intuitive for users.
Fixes https://github.com/twentyhq/twenty/issues/17544
**Problem**
When users create custom objects with short acronym names like "O&J",
the system generates an object name oJ. When creating relation fields,
the morph field name was built using string concatenation:
const morphFieldName = `target${capitalize("oJ")}`; // → "targetOJ"
This produced "targetOJ", which failed validation because the camelCase
check performed in `validateFlatFieldMetadataName` (camelCase(name) ===
name) returns "targetOj" for "targetOJ". The issue comes from
consecutive camelCase() operations.
**Solution**
Actually, the `camelCase(name) === name` check is questionnable.
What we want to check is that a name is in camelCase format, not that it
corresponds to the camelCase version of a given string, while that's we
are doing here. lodash does not provide camelCase validator, only
camelCase convertor, so we used it as a way to validate the format of
the name.
We may feel like `camelCase(name) === name` checks whether a name is
camel-cased, but in addition to that it is also checking for a camel
case "idempotency" we don't necessarily have and do not need: for
instance if an object's name is "iOS" (which could be inferred from a
label "I O S"), it won't pass the check: camelCase("iOS") is "ios" and
"ios" !== "iOS".
The existing check with
`STARTS_WITH_LOWER_CASE_AND_CONTAINS_ONLY_CAPS_AND_LOWER_LETTERS_AND_NUMBER_STRING_REGEX`
acts as a camel case validator, so we don't need that camelCase() check.
## Summary
- **Increase `--max-turns` from 50 to 200** — Claude was hitting the
turn limit before getting to `gh pr create`, resulting in branches with
no PR
- **Add common bash commands to `--allowedTools`** — `rm`, `find`,
`grep`, `cat`, `ls`, `head`, `tail`, `wc`, `sort`, `uniq`, `mkdir`,
`cp`, `mv`, `touch`, `chmod`, `echo`, `curl`, `cd`, `pwd`, `diff`,
`xargs`, `awk`, `cut`, `tee`, `tr` — denied commands were wasting turns
on retries
## Context
Both [run
21594509983](https://github.com/twentyhq/twenty/actions/runs/21594509983)
and [run
21595364188](https://github.com/twentyhq/twenty/actions/runs/21595364188)
failed with `error_max_turns` after exhausting 50 turns. Permission
denials on `rm` and `find` contributed to wasted turns.
## Test plan
- [ ] Tag `@claude` on an issue with a code change request — verify it
completes and creates a PR
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
## Summary
- Replace all `depot-ubuntu-24.04-8` runner references with the
equivalent GitHub-hosted `ubuntu-latest-8-cores` runner
- Updated across 4 workflow files: `ci-front.yaml`, `ci-server.yaml`,
`ci-emails.yaml`, `ci-sdk.yaml`
- Also updated cache key names in `ci-front.yaml` that referenced the
depot runner name
## Test plan
- [ ] Verify CI workflows run successfully on the new GitHub-hosted
larger runners
- [ ] Confirm cache keys work correctly with the updated naming
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
- Introduced `CommandMenuNavigationMenuItemEditPage` for editing navigation menu items.
- Updated `COMMAND_MENU_PAGES_CONFIG` to include the new edit page.
- Added state management for selected navigation menu item in edit mode.
- Enhanced `WorkspaceNavigationMenuItems` to support opening the edit page and handling edit mode interactions.
These changes improve the user experience by allowing direct editing of navigation menu items within the command menu.
# Introduction
In this PR we're deprecating the object metadata standard id and
replacing it to the universalIdentifier usage
As we've totally removed its insertion for both new field and object in
https://github.com/twentyhq/twenty/pull/17572
## Note
- Removed upgrade commands before `1.17`
## Summary
- **Set `cancel-in-progress: false`** — Claude's own comments (e.g.
error reports) were triggering new workflow runs via `issue_comment`,
which cancelled the in-progress Claude run before the new run's `if`
condition could skip it. Disabling cancel-in-progress prevents this.
- **Filter out Bot users in `if` conditions** — Added
`github.event.comment.user.type != 'Bot'` so Claude's own comments don't
start job evaluation at all.
- **Add back `additional_permissions: actions: read`** — Lets Claude
read CI failure logs to help debug broken builds.
- **Remove `discussion_comment` trigger and job** — `claude-code-action`
doesn't support this event type yet (throws `Unsupported event type:
discussion_comment`). Listed as planned in their security.md.
## Context
Claude runs were consistently failing with `The operation was canceled`
because:
1. Claude posts an error/status comment back to the issue
2. That comment triggers a new `issue_comment` workflow run on the same
issue number
3. The concurrency group cancels the in-progress run before the new run
evaluates its `if` and skips
## Test plan
- [ ] Tag `@claude` on an issue — should run to completion without being
cancelled
- [ ] Verify Claude's own reply comments don't trigger new workflow runs
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
## Summary
- `allowed_tools` is not a valid input for `claude-code-action@v1` — it
was silently ignored (visible as a warning in CI logs)
- This caused Claude to lack file write permissions, preventing it from
making actual code changes
- Move the tools list into `claude_args` as `--allowedTools` where it's
actually parsed
## Test plan
- [ ] Trigger `@claude` on a cross-repo issue requesting a code change —
verify it can edit files and create a PR
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
# Introduction
Important note: This PR officially deprecates the `standardId`, about to
drop col and entity property after this has been merged
Important note2: Haven't updated the optimistic tool to also update the
universal identifier aggregators only the ids one, they should not be
consumed in the runner context -> need to improve typing or either the
optimistic tooling
In this PR we're introducing all the devxp allowing future metadata
incremental universal migration -> this has an impact on all existing
metadata actions handler ( explaining its size )
This PR also introduce workspace agnostic create update actions runner
for both field and object metadata in order to battle test the described
above devxp
Noting that these two metadata are the most complex to handle
Notes:
- A workspace migration is now highly bind to a
`applicationUniversalIdentifier`. Though we don't strictly validate
application scope for the moment
## Next
Migrate both object and field builder to universal comparison
## Universal Actions vs Flat Actions Architecture
### Concept
The migration system uses a two-phase action model:
1. **Universal Actions** - Actions defined using `universalIdentifier`
(stable, portable identifiers like `standardId` + `applicationId`)
2. **Flat Actions** - Actions defined using database `entityId` (UUIDs
specific to a workspace)
### Why This Separation?
- **Universal actions are portable**: They can be serialized, stored,
and replayed across different workspaces
- **Flat actions are executable**: They contain the actual database IDs
needed to perform operations
- **Decoupling**: The builder produces universal actions; the runner
transpiles them to flat actions at execution time
### Transpiler Pattern
Each action handler must implement
`transpileUniversalActionToFlatAction()`:
```typescript
@Injectable()
export class CreateFieldActionHandlerService extends WorkspaceMigrationRunnerActionHandler(
'create',
'fieldMetadata',
) {
override async transpileUniversalActionToFlatAction(
context: WorkspaceMigrationActionRunnerArgs<UniversalCreateFieldAction>,
): Promise<FlatCreateFieldAction> {
// Resolve universal identifiers to database IDs
const flatObjectMetadata = findFlatEntityByUniversalIdentifierOrThrow({
flatEntityMaps: allFlatEntityMaps.flatObjectMetadataMaps,
universalIdentifier: action.objectMetadataUniversalIdentifier,
});
return {
type: action.type,
metadataName: action.metadataName,
objectMetadataId: flatObjectMetadata.id, // Resolved ID
flatFieldMetadatas: /* ... transpiled entities ... */,
};
}
}
```
### Action Handler Base Class
`BaseWorkspaceMigrationRunnerActionHandlerService<TActionType,
TMetadataName>` provides:
- **`transpileUniversalActionToFlatAction()`** - Abstract method each
handler must implement
- **`transpileUniversalDeleteActionToFlatDeleteAction()`** - Shared
helper for delete actions
## FlatEntityMaps custom properties
Introduced a `TWithCustomMapsProperties` generic parameter to control
whether custom indexing structures are included:
- **`false` (default)**: Returns `FlatEntityMaps<MetadataFlatEntity<T>>`
- used in builder/runner contexts
- **`true`**: Returns the full maps type with custom properties (e.g.,
`byUserWorkspaceIdAndFolderId`) - used in cache contexts
## Create Field Actions Refactor
Refactored create-field actions to support relation field pairs
bundling.
**Problem:** Relation fields (e.g., `Attachment.targetTask` ↔
`Task.attachments`) couldn't resolve each other's IDs during
transpilation because they were in separate actions with independent
`fieldIdByUniversalIdentifier` maps.
**Solution:**
- Removed `objectMetadataUniversalIdentifier` from
`UniversalCreateFieldAction` and `objectMetadataId` from
`FlatCreateFieldAction` - each field now carries its own
- Runner groups fields by object internally and processes each table
separately
- Split aggregator into two focused utilities:
- `aggregateNonRelationFieldsIntoObjectActions` - merges non-relation
fields into object actions
- `aggregateRelationFieldPairs` - bundles relation pairs with shared
`fieldIdByUniversalIdentifier`
Claude was blocked from writing files, using git/gh, and fetching
web content because only a few Bash patterns were pre-approved.
Add Edit, Write, WebFetch, git, gh, sed, and python3 to the
allowed_tools list.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The claude-code-action needs id-token: write to fetch an OIDC token.
The claude-cross-repo job was missing its permissions block entirely,
causing it to fail with "Could not fetch an OIDC token".
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
## Summary
- Move build/env/DB setup out of the GitHub Actions workflow into an
on-demand script (`packages/twenty-utils/setup-dev-env.sh`) that Claude
invokes only when needed
- Add Nx/build cache restore/save steps to speed up repeated runs
- Add `allowed_tools` so Claude can run the setup script and common dev
commands (nx, jest, yarn)
- Add `PG_DATABASE_URL` env var via `settings` for the postgres MCP
server
- Remove unnecessary `actions: read` permission grant
- Document the lazy setup pattern in CLAUDE.md
This makes read-only tasks (code review, architecture questions, docs)
fast by skipping the ~2min build/setup overhead, while still letting
Claude run tests and builds when it needs to.
## Test plan
- [ ] Comment `@claude what is the architecture of the backend?` —
should answer fast without running setup
- [ ] Comment `@claude run the twenty-server unit tests` — should call
setup script first, then run tests
- [ ] Verify cross-repo dispatch still works
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
- Added an `inverted` prop to `CancelButton`, allowing for a tertiary button style.
- Updated `SaveButton` to support an `inverted` prop, changing its appearance based on the prop value.
- Modified `SaveAndCancelButtons` to pass the `inverted` prop to both buttons, ensuring consistent styling.
These changes improve the visual flexibility of the buttons in the settings module, enhancing user experience.
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
## Problem
Favorite records (`NavigationMenuItems`) were not showing in the sidebar
under Favorites as the BE was returning `null` for
`targetRecordIdentifier`.
## Root cause
The `targetRecordIdentifier` resolver used `context.req`, which does not
have the typed `WorkspaceAuthContext` shape.
## Fix
Use `getWorkspaceAuthContext()` instead of `context.req` so the resolver
receives the typed auth context.
## Summary
- Add `--model opus` to `claude_args` in both the direct and cross-repo
Claude jobs
## Test plan
- [ ] Merge and trigger @claude — verify it reports running Opus 4.5
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
## Summary
- Add `repository_dispatch` trigger to `claude.yml` so `@claude`
mentions in `twentyhq/core-team-issues` get forwarded here
- New `claude-cross-repo` job: builds a prompt from the dispatch
payload, runs Claude Code against the twenty codebase, and posts a link
back to the source issue
- Switch both jobs to use `claude_code_oauth_token` instead of
`anthropic_api_key`
A companion workflow (`claude-dispatch.yml`) was pushed to
`twentyhq/core-team-issues` to send the dispatches.
## Test plan
- [x] Dispatch workflow in core-team-issues triggers successfully
(tested via issue #2194)
- [ ] After merge, verify `repository_dispatch` triggers the
`claude-cross-repo` job in twenty
- [ ] Verify Claude responds and posts a link back to the source issue
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
- Introduced `useNavigationMenuEditModeActions` for managing edit mode actions, including entering and canceling edit mode.
- Added `useNavigationMenuItemsDraftState` to handle draft state of navigation menu items, determining workspace items based on edit mode.
- Created `isNavigationMenuInEditModeState` and `navigationMenuItemsDraftState` atoms for managing edit mode status and draft items.
These additions enhance the functionality for editing navigation menu items, improving user experience and customization options.
- Introduced the IS_NAVIGATION_MENU_ITEM_EDITING_ENABLED flag across the GraphQL schema and server-side enums.
- Updated the workspace entity manager tests to include the new feature flag.
- Enhanced the seed feature flags utility to support the new flag.
These changes enable editing capabilities for navigation menu items, improving customization options.
## 🤖 Installing Claude Code GitHub App
This PR adds a GitHub Actions workflow that enables Claude Code
integration in our repository.
### What is Claude Code?
[Claude Code](https://claude.com/claude-code) is an AI coding agent that
can help with:
- Bug fixes and improvements
- Documentation updates
- Implementing new features
- Code reviews and suggestions
- Writing tests
- And more!
### How it works
Once this PR is merged, we'll be able to interact with Claude by
mentioning @claude in a pull request or issue comment.
Once the workflow is triggered, Claude will analyze the comment and
surrounding context, and execute on the request in a GitHub action.
### Important Notes
- **This workflow won't take effect until this PR is merged**
- **@claude mentions won't work until after the merge is complete**
- The workflow runs automatically whenever Claude is mentioned in PR or
issue comments
- Claude gets access to the entire PR or issue context including files,
diffs, and previous comments
### Security
- Our Anthropic API key is securely stored as a GitHub Actions secret
- Only users with write access to the repository can trigger the
workflow
- All Claude runs are stored in the GitHub Actions run history
- Claude's default tools are limited to reading/writing files and
interacting with our repo by creating comments, branches, and commits.
- We can add more allowed tools by adding them to the workflow file
like:
```
allowed_tools: Bash(npm install),Bash(npm run build),Bash(npm run lint),Bash(npm run test)
```
There's more information in the [Claude Code action
repo](https://github.com/anthropics/claude-code-action).
After merging this PR, let's try mentioning @claude in a comment on any
PR to get started!
---------
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
- Added `IconColor` to the `useGetStandardObjectIcon` hook for improved icon color customization.
- Simplified logic for determining icon background color based on the presence of `targetRecordId` and `viewId`.
- Consolidated avatar rendering logic to reduce redundancy and improve readability.
These changes enhance the visual consistency and flexibility of navigation menu items.
- Introduced `getNavigationMenuItemIconColors` utility to manage icon colors based on the theme.
- Updated `CurrentWorkspaceMemberNavigationMenuItems` and `NavigationDrawerItemForObjectMetadataItem` to utilize the new icon color utility.
- Refactored `NavigationMenuItemIcon` to include a styled background for icons, improving visual consistency.
- Adjusted `NavigationDrawerItem` and `NavigationDrawerSubItem` to accept and apply background colors for icons.
These changes improve the visual representation of navigation items and ensure consistent theming across the application.
## Summary
When a select option label is long, it was pushing the aggregate value
(e.g., '12.6m') out of view because the tag had `flex-shrink: 0`.
Changed to `min-width: 0` and `overflow: hidden` to allow the tag to
shrink and truncate with ellipsis, keeping the aggregate value visible.
## Changes
- Modified `StyledTag` in
[RecordBoardColumnHeader.tsx](cci:7://file:///root/74/bronze/twenty/packages/twenty-front/src/modules/object-record/record-board/record-board-column/components/RecordBoardColumnHeader.tsx:0:0-0:0)
to allow shrinking
The
[Tag](cci:1://file:///root/74/bronze/twenty/packages/twenty-ui/src/components/tag/Tag.tsx:100:0-141:2)
component already has built-in text truncation with
`OverflowingTextWithTooltip`, so this change enables that functionality
to work properly.
Fixes#17350
```**
---------
Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
https://github.com/user-attachments/assets/c4e63edb-6e98-44bc-841f-ee110ae712d4
How it works
- `manifest.json` files are now committed when apps are published to our
repo
- to display available apps, from the server, we read into our github
repo, using a pod-scoped cache
- feature flagged
- app installation will be behind permission gate MARKETPLACE_APPS
Limitations and what is yet to develop
- content and settings tabs
- installed apps tab
- app installation
- test and potentially fix reading from .manifest.json once [Reshape
manifest
structure](https://github.com/twentyhq/core-team-issues/issues/2183) is
done. additional work is expected on assets notably. (couldnt properly
do it here as manifest.json will only be committed after this pr)
- we only read in community/ folder for now - we may want tochange that
- the cache is rather artisanal for now and scoped by pod - we may want
to change that
Fixes#17111
### Problem
When creating a Relation field, after deselecting the pre-selected
default object (e.g., Company) and selecting a different object (e.g.,
Opportunity), both objects would end up being selected, showing "2
Objects" instead of just the newly selected one.
### Root Cause
The `initialMorphRelationsObjectMetadataIds` array was being recreated
on every component render, causing react-hook-form's `Controller` to
treat the `defaultValue` prop as a new value and re-apply it after user
changes.
### Solution
1. **Memoized the initial value**: Used `useMemo` to ensure
`initialMorphRelationsObjectMetadataIds` has a stable reference across
renders
2. **Added initialization guard**: Used `useEffect` with a `useRef` flag
to ensure the default value is only set once during component mount
3. **Maintained Controller defaultValue**: Kept the `defaultValue` prop
on the Controller, which now works correctly with the stable memoized
value
### Changes
- `SettingsDataModelFieldRelationForm.tsx`: Added memoization and
initialization guard to prevent default value re-application
---------
Co-authored-by: Etienne <45695613+etiennejouan@users.noreply.github.com>
Removes the versioning system for logic functions (`latestVersion`,
`publishedVersions`) and simplifies the file storage structure.
### Changes
- Remove `publishOneLogicFunctionOrFail` and publishing logic from
workflow status updates
- Add `createLogicFunctionFromExistingLogicFunction` to duplicate logic
functions when creating draft workflow versions
- Update `createDraftStep` to create a new logic function copy instead
of referencing the same one
- Migrate file storage to v2 endpoints with
`applicationUniversalIdentifier`
- Unify path structure: source files at `source/workflow/{id}/`, built
files at `built-logic-function/workflow/{id}/`
- Store full paths in `sourceHandlerPath` and `builtHandlerPath` entity
fields
## Summary
- Remove `latestVersion` and `publishedVersions` columns from
`LogicFunction` entity
- Remove version parameter from logic function execution, build, and
source code retrieval
- Update all related services, DTOs, utilities, and tests
- Add database migration to drop the version columns
# Introduction
In this PR we're migrating both the `field` and `object` metadata to be
using the new `FlatEntityFromV2` that requires all the
`UniversalFlatEntityExtraProperties` to be spread at the flat entity
root.
This means that we have to update all of their flat declaration
This type swap allows to isole a specific entity migration into his own
type scope and avoid to have everything handled at once
```ts
/**
* Currently under migration but aims to replace FlatEntity afterwards
*/
export type FlatEntityFromV2<
TEntity,
TMetadataName extends AllMetadataName | undefined = undefined,
TInnerFlatEntity extends { __universal?: unknown } = FlatEntityFrom<
TEntity,
TMetadataName
>,
> = Omit<TInnerFlatEntity, '__universal'> & TInnerFlatEntity['__universal'];
```
## Impact
Both object and field:
- Create input transpilation utils
- from entity to flat tools
- mocks
## Note
Removed from the universal extra properties the jsonb properties that do
not contain a serialized
## Next
Next step is to incrementally make the builder and runner expect
`UniversalFlatEntity` for both of these metadata
This way we will be able to fully migrate an entity e2e typesafely
React was warning that custom props like `isExpanded` and `allMatched`
were being passed to DOM elements (SVG icons and divs), which only
accept standard HTML/SVG attributes.
## Changes
Added `shouldForwardProp` configuration to styled components to filter
out custom props before they reach the DOM:
```typescript
import isPropValid from '@emotion/is-prop-valid';
const StyledChevronIcon = styled(IconChevronDown, {
shouldForwardProp: (prop) =>
isPropValid(prop) && prop !== 'isExpanded',
})<{ isExpanded: boolean }>`
transform: ${({ isExpanded }) => isExpanded ? 'rotate(180deg)' : 'rotate(0deg)'};
`;
```
This pattern is already used in other components (e.g.,
`NavigationDrawerItem`, `TableRow`) and ensures custom props are
available for styling logic but don't leak to the underlying DOM
element.
## Files Modified
- `FieldsWidgetSectionContainer.tsx` - `isExpanded` on icon component
- `UnmatchColumnBanner.tsx` - `isExpanded`, `allMatched` on icon, div,
and Banner components
<!-- 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: Devessier <baptiste@devessier.fr>
**Fixes #17420**
### Problem
The billing/pricing display showed inconsistencies:
- "Get Started" / trial signup flow: simple `$9` (no decimals)
- Pricing page: `$9.00` (with decimals)
For yearly plans (with discount), the effective monthly price might not
always be an integer, leading to messy floats in UI (e.g., 7.5 showing
as 7.499999 or inconsistent formatting).
### Solution
Added a helper function `formatYearlyPriceIfNeccessary` that:
- Only applies to yearly subscriptions (`SubscriptionInterval.Year`)
- Calculates effective monthly price (`price / 12`)
- Returns an integer if the result is whole (`Number.isInteger`)
- Otherwise formats to exactly 2 decimal places (`toFixed(2)` → Number)
This ensures clean, consistent display:
- Whole numbers: no decimals (e.g., $9 → 9)
- Fractional: precise 2 decimals (e.g., $81 yearly → 6.75)
### Changes
- Added `formatYearlyPriceIfNeccessary` function (likely in a
utils/pricing file — specify the file path if you know it, e.g.
`src/utils/pricing.ts`)
- Applied it where yearly effective prices are rendered (e.g., in
BillingPlan component, Pricing page, Signup flow — list the
files/components you changed)
### Before / After
- Before (yearly example with discount): Might show 7.5 or 7.499999
- After: Always 7.5 (or 7 if integer)
### Testing
- Ran locally with `yarn dev`
- Checked signup flow and pricing page: prices now consistent and clean
- Verified non-yearly plans unchanged
Let me know if there's a preferred formatting style (e.g., always show
.00, or use Intl.NumberFormat) or if this should be applied in more
places!
Thanks for the great project, my first contribution btw
Closes#17420
---------
Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com>
Co-authored-by: Etienne <45695613+etiennejouan@users.noreply.github.com>
## Context
The previous WorkspaceAuthContext was a single interface with many
optional fields, making it unclear which fields are available in
different authentication scenarios. This made the code harder to reason
about and required runtime checks scattered throughout the codebase.
## Changes
- Introduced a discriminated union type for WorkspaceAuthContext with
four specific variants:
-> UserWorkspaceAuthContext - for authenticated users
-> ApiKeyWorkspaceAuthContext - for API key authentication
-> ApplicationWorkspaceAuthContext - for application-based auth
-> SystemWorkspaceAuthContext - for system/internal operations
- Added type guard functions (isUserAuthContext, isApiKeyAuthContext,
etc.) for safe type narrowing
- Added builder utilities (buildUserAuthContext, buildApiKeyAuthContext,
etc.) to construct each context variant with proper type safety
- Refactored WorkspaceAuthContextMiddleware to use the new builders
instead of constructing a loosely-typed object
- Moved the type definition from twenty-orm/interfaces/ to
core-modules/auth/types/ for better organization
- Updated all consumers across query runners, tool providers, and
modules to use the new type location
## Notes
- I had to query User and WorkspaceMember in some parts of tool module
that were expecting userWorkspaceId but not the rest of
UserWorkspaceAuthContext (that should be required with the new proper
type otherwise it would break a lot of logic and mostly permissions with
the newly added RLS -> This is what we expect from
UserWorkspaceAuthContext and how it's done in the "normal" path in HTTP
middleware)
- WorkspaceMember is in the cache already but ideally we should move
User (And Workspace?) in the cache as well to avoid querying the DB
after each request (this is also valid for HTTP middleware when we
hydrate the Request object btw)
Fixes: #16872
## Summary
Fixes the DateTimePicker to respect user's 12H/24H time format
preference. Previously, the time display at the top of the
DateTimePicker always showed 24-hour format (e.g., "14:30") regardless
of the user's time format setting.
## Screenshots
_After:_ Time respects user preference, showing 12H with AM/PM (e.g.,
"03:28 PM") or 24H format
<img width="357" height="491" alt="image"
src="https://github.com/user-attachments/assets/4a03f195-a52b-4f74-84ba-c5eb2fc6c8c1"
/>
## Implementation Details
### Changes Made:
1. **TimeMask.ts** - Added `getTimeMask()` to return appropriate mask
pattern based on time format
2. **TimeBlocks.ts** - Restored as constant file per linting
requirements
3. **getTimeBlocks.ts** (new) - Dynamic block generator supporting 12H
(1-12 + AM/PM) and 24H (0-23)
4. **DateTimeBlocks.ts** - Simplified to static constant
5. **getDateTimeMask.ts** - Updated to accept and use `timeFormat`
parameter
6. **DateTimePickerInput.tsx** - Dynamically generates mask and blocks
based on user's time format preference, increased input width to
accommodate AM/PM
7. **useParseJSDateToIMaskDateTimeInputString.ts** - Formats dates with
correct time pattern
8. **useParseDateTimeInputStringToJSDate.ts** - Parses dates with
correct time pattern
9. **date-utils.ts** - Added `getTimePattern()` helper (returns 'hh:mm
a' for 12H, 'HH:mm' for 24H)
10. **parseDateTimeToString.ts** - Added optional `timeFormat` parameter
for consistency
### Technical Approach:
- Leverages existing `useDateTimeFormat()` hook to get user's
`timeFormat` preference
- Supports `TimeFormat.SYSTEM`, `TimeFormat.HOUR_12`, and
`TimeFormat.HOUR_24`
- Uses IMask blocks with appropriate ranges: 1-12 for 12H, 0-23 for 24H
- Adds 'aa' (AM/PM) block for 12-hour format with enum validation
---------
Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
Fix https://github.com/twentyhq/twenty/issues/17524
Recent update meant to support variable with spaces and dots broken the
database event variables. Inserting a variable `My Name` will be
inserted as `{{trigger.[My Name]}}`
For example, for a database event, we send `trigger.properties.after.id`
instead of `id`.
With the new code, we will consider `properties.after.id` as a variable
to wrap in `[]`, giving `trigger.[properties.after.id]` which cannot be
resolve.
Let's only support variable with spaces. Removing the logic to wrap
variable with dots.
# Introduction
In this PR we're refactoring the `FlatEntity` type to become a superset
of the `UniversalFlatEntity`.
Right now we're storing all the extra properties in `__universal`
property, at some point it might just be sibling to other entity and we
might rely on the `propertiesToCompare` constants and TypeScript
allowing passing a superset type into a smaller subset type
## FromTo utils
The entity to flat entity method now computes the universal information,
standardized a typing and pattern to do
## Example
Also strictly type
```ts
"bbb019ea-6205-498c-aea5-67bc53bce8a9": {
"workspaceId": "20202020-1c25-4d02-bf25-6aeccf7ea419",
"universalIdentifier": "20202020-d111-4d11-8d11-da5ab0a11002",
"applicationId": "d01b010d-b984-465b-b40b-370e954e5188",
"id": "bbb019ea-6205-498c-aea5-67bc53bce8a9",
"pageLayoutTabId": "791a512f-169f-4209-b731-aa86716668c6",
"title": "Deals by Company",
"type": "GRAPH",
"objectMetadataId": "9e14efea-df5b-4c0e-aba9-cfe455f32397",
"gridPosition": { "row": 0, "column": 6, "rowSpan": 6, "columnSpan": 6 },
"configuration": {
"color": "orange",
"orderBy": "FIELD_ASC",
"timezone": "UTC",
"displayLegend": true,
"displayDataLabel": false,
"showCenterMetric": true,
"configurationType": "PIE_CHART",
"firstDayOfTheWeek": 0,
"aggregateOperation": "COUNT",
"groupBySubFieldName": "name",
"groupByFieldMetadataId": "6673ff18-63d2-47a1-8f85-2b9b09ca27a5",
"aggregateFieldMetadataId": "8d64ee41-5dd4-4de6-945a-7c0c18399715"
},
"createdAt": "2026-01-28T14:08:52.140Z",
"updatedAt": "2026-01-28T14:08:52.140Z",
"deletedAt": null,
"__universal": {
"universalIdentifier": "20202020-d111-4d11-8d11-da5ab0a11002",
"applicationUniversalIdentifier": "20202020-64aa-4b6f-b003-9c74b97cee20",
"pageLayoutTabUniversalIdentifier": "20202020-d011-4d11-8d11-da5ab0a01001",
"objectMetadataUniversalIdentifier": "20202020-9549-49dd-b2b2-883999db8938",
"gridPosition": {
"row": 0,
"column": 6,
"rowSpan": 6,
"columnSpan": 6
},
"configuration": {
"color": "orange",
"orderBy": "FIELD_ASC",
"timezone": "UTC",
"displayLegend": true,
"displayDataLabel": false,
"showCenterMetric": true,
"configurationType": "PIE_CHART",
"firstDayOfTheWeek": 0,
"aggregateOperation": "COUNT",
"groupBySubFieldName": "name",
"aggregateFieldMetadataUniversalIdentifier": "20202020-d01a-4131-8a31-f123456789ab",
"groupByFieldMetadataUniversalIdentifier": "20202020-cbac-457e-b565-adece5fc815f"
}
}
},
```
## Summary
Fixes an issue where the AI chat would show loading shimmers
indefinitely when opened on a workspace with no conversation history.
**Root cause:** The `isLoading` state in `useAgentChat` included
`!currentAIChatThread`. On workspaces with no chat threads,
`currentAIChatThread` remained `null`, causing `isLoading` to be
permanently `true`.
**Changes:**
- Remove `!currentAIChatThread` from `isLoading` calculation in
`useAgentChat` - this state should only reflect streaming/file selection
status
- Auto-create a chat thread in `useAgentChatData` when the threads query
returns empty, ensuring a valid thread exists for the `useChat` hook to
initialize properly
- Add primary font color to empty state title for better visibility
## Test plan
1. Create a new workspace or use a workspace with no AI chat history
2. Open the AI chat
3. Verify the empty state shows (not infinite loading shimmer)
4. Send a message and verify it works correctly
Made with [Cursor](https://cursor.com)
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Fix https://github.com/twentyhq/twenty/issues/17507
The canvas layout mode is expected to render a single widget.
Previously, we threw an error when trying to render a canvas layout with
no widgets at all. This worked fine when we immediately rendered a
widget that used a single-widget canvas layout.
However, the active tab ID is shared across all page layouts with the
same ID, which doesn’t work well with conditional display. The available
tabs may depend on conditions such as the device displaying the page.
For example, when displaying a Note on the show page, the Note widget
appears on a separate tab. On mobile and in the side panel, however, it
is moved to the Home tab.
After that, if the user opens a Note in the side panel, the default
active tab might be the Note tab, which uses a _canvas_ layout mode and
would have no widgets at all when rendered in the side panel. This leads
to the error mentioned above.
The simple workaround is to wait one tick. An `Effect` component then
detects that the active tab doesn’t exist in the list of allowed tabs
and switches to another tab by default.
This feels more like a workaround than a proper solution, but I prefer
to fix it this way for now. We can later revisit this and design a
better separation for the selected tab ID state.
## Before
https://github.com/user-attachments/assets/500e332c-1623-48e5-b69b-5a4fa4e5e28d
## After
https://github.com/user-attachments/assets/b39dcf96-1852-42e4-a8cc-a79bf9036579
This PR adds what is required to handle soft-delete and restore SSE
events in virtualized table and board.
Restore is not handled in board for now as it requires respecting sorts
when inserting record ids.
Since virtualized table is refetching small chunks, we just refetch for
now.
The long term goal is to handle event handling without refetching in all
main components, and also handle SSE events that have the same origin
that the current tab. But for now we implement what is easily doable.
# QA
Delete between table and board (delete only) :
https://github.com/user-attachments/assets/715dd44a-007a-44ab-bf49-5ef039cd57c3
Delete and restore between table and table :
https://github.com/user-attachments/assets/f2122519-e969-491f-b71c-018d0f85bd86
## Summary
Migrates trigger entities (`CronTriggerEntity`,
`DatabaseEventTriggerEntity`, `RouteTriggerEntity`) into
`ServerlessFunctionEntity` by storing trigger settings as JSONB columns
directly on the serverless function. This simplifies the architecture
since these relationships were effectively one-to-one.
## Changes
### Schema Changes
- Added three new nullable JSONB columns to `ServerlessFunctionEntity`:
- `cronTriggerSettings` - stores cron pattern
- `databaseEventTriggerSettings` - stores event name and updated fields
filter
- `httpRouteTriggerSettings` - stores path, HTTP method, auth
requirements, and forwarded headers
### Core Logic Updates
- `CronTriggerCronJob` - now queries `ServerlessFunctionEntity` directly
instead of `CronTriggerEntity`
- `CallDatabaseEventTriggerJobsJob` - now queries
`ServerlessFunctionEntity` directly
- `RouteTriggerService` - now queries `ServerlessFunctionEntity`
directly
- `ApplicationSyncService` - extracts trigger settings from manifest and
writes to serverless function
## Context
Introduces a middleware that automatically sets the workspace auth
context in AsyncLocalStorage for HTTP requests, making it available
throughout the request lifecycle without explicit parameter passing.
The motivation behind this change is to reduce boilerplate and simplify
the developer experience when working with workspace data in HTTP
request handlers.
The Problem (Before)
Every HTTP request handler that needed to access workspace data had to:
- Extract auth-related info from decorators (@AuthWorkspace(),
@AuthUserWorkspaceId(), etc.) in controller/resolver and pass down to
services
- Build or pass the authContext explicitly (sometimes with type
assertion which was flaky)
Then call executeInWorkspaceContext(authContext, async () => { ... })
## Changes
- Add WorkspaceAuthContextMiddleware that extracts auth context from the
request and stores it in AsyncLocalStorage
- Register middleware for GraphQL, metadata, and REST routes (runs after
hydration middlewares)
- Simplify executeInWorkspaceContext signature: fn is now the first
parameter, authContext is optional second
- If authContext is not provided, it's automatically retrieved from the
storage (set by middleware)
- Update all callers (~120 files) to use the new parameter order
- Fixes a bug in search where system auth context was used, bypassing
RLS feature.
This PR reverts the change that was made to turn `Date` objects into ISO
string, because right now there are too many places in the code that use
both, either Date or ISO string from an entity returned by the querying
layer.
Unifying everything with ISO string is clearly the long term goal, but
right now it is too difficult, we should first refactor each part to ISO
string, then at the end remove Date from the codebase.
This PR fixes a bug that arises in `formatResult` following up the
recent refactor for DATE_TIME :
https://github.com/twentyhq/twenty/pull/17407
In the case of workflows, we pass a plain object to `formatResult` :
```ts
{
before: null,
after: '2026-01-27T10:59:15.525Z'
}
```
So a case has been added to handle this.
@Weiko are we ok with this more restrictive else-if part in this util ?
We could also just put a `continue` for unknown shapes.
Added example output showing what the roles table should look like after
creating the postgres role
Co-authored-by: Tom Larson <tomlarson@Toms-MacBook-Pro.local>
## Implement Navigation Menu Items Frontend
Implements the frontend for navigation menu items, the new system
replacing favorites.
### Changes
- Added GraphQL fragments and queries for navigation menu items
- Added hooks for managing navigation menu items (create, update,
delete, sorting, filtering)
- Updated components to use navigation menu items instead of favorites
- Added test coverage for utility functions
### Migration Note
The favorites and navigation menu item modules currently exist in
parallel. The favorites code will be removed once all data has been
migrated to navigation menu items.
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> Replaces Favorites with feature-flagged `NavigationMenuItem` across
frontend and backend, while keeping Favorites as fallback until
migration completes.
>
> - UI: new `navigation-menu-item` components (folders, orphan items,
drag provider/droppable, icons, skeleton), dispatcher components to
switch from Favorites, and updated “Add to favorites” action to create
`NavigationMenuItem` when `IS_NAVIGATION_MENU_ITEM_ENABLED`
> - DnD: shared `validateAndExtractFolderId` and droppable id utils
moved to `ui/layout/draggable-list`; favorites DnD updated to use shared
utils
> - GraphQL (client): add fragments, queries, mutations, hooks
(create/update/delete/find), and generated types; added
`RecordIdentifier` and `targetRecordIdentifier` on `NavigationMenuItem`
> - Prefetch: new prefetch state/effect for navigation menu items; skip
favorites prefetch when flag enabled
> - Backend: add DTOs (`NavigationMenuItem`, `RecordIdentifier`),
resolver `targetRecordIdentifier` field, service logic to fetch record
identifiers with permission-aware access and image signing,
`getRecordImageIdentifier` util, entity relation to `view`, and
migration adding FK on `viewId`
> - Feature flags & seeding: add `IS_NAVIGATION_MENU_ITEM_ENABLED` to
enums, dev seeder enables it; standard app seeds workspace navigation
menu items instead of favorites when flag on
> - Tests: add unit tests for sorting/labels/folder id and related utils
>
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
c99746f08b. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
---------
Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
Co-authored-by: Aman Raj <92664006+araj00@users.noreply.github.com>
Co-authored-by: Félix Malfait <felix@twenty.com>
Co-authored-by: Paul Rastoin <45004772+prastoin@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: github-actions <github-actions@twenty.com>
## Today
CTA is confusing
<img width="1442" height="947" alt="image"
src="https://github.com/user-attachments/assets/d5dd6229-e78d-4068-acbc-b5766b725b14"
/>
## Summary
- Replace the "Add account" button text with "Finish Setup" at the end
of the calendar account configuration flow
- Change the button icon from a plus sign (`IconPlus`) to a floppy disk
(`IconDeviceFloppy`) to better reflect saving/completing the setup
This PR fixes the inconsistency between Calendar and Table views when
trying to edit createdAt date field.
Previously, calendar cards could be dragged even though the createdAt
field are UI read-only, resulting in the confusing and inconsistent
behavior compared to the Table view where the field is not editable.
The issue was caused by the drag logic checking only user permissions
and not the field’s UI read-only metadata. This change updates the
calendar drag logic to also check for isUIReadOnly, ensuring that
records are not draggable when the selected calendar date field is
read-only.
---------
Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
# Introduction
Previously the command would have been `old` renaming only any `owner`
field on `opportunity` object
Now we also search for any colliding `joinColumnName` with `ownerId`
which is also introduced by the new standard owner field
In a nutshell: previously gracefully handling existing custom `owner`
field collision but not for RELATION types that has an additional
collision surface: `joinColumnName`
Related
https://github.com/twentyhq/twenty/issues/17413#issuecomment-3799872079
Fixes issue where focusing the record title input would reset the title
to empty. This ensures the draft value is properly initialized from the
field value if it's undefined or empty when the input is focused.
Fixes#17437
---------
Co-authored-by: Daniel <daniel@example.com>
Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
# Introduction
In this PR we're introducing mainly two branded type signatures for both
`JsonbProperty` entities properties and `SerializedRelation` (jsonb
serialized property storing another entity id).
Allowing to dynamically map over them later in order to build universal
`jsonb` `serialized` relations.
## `JsonbProperty`
A branded wrapper type that marks entity properties stored as PostgreSQL
JSONB columns. It adds a phantom brand `__JsonbPropertyBrand__` to
object types while leaving primitives unchanged. The branded key is
optional and typed as never, also omitted when transpiled to
`UniversalFlat`
**Should be used at entities lvl only:**
```typescript
@Column({ type: 'jsonb', nullable: false })
gridPosition: JsonbProperty<GridPosition>;
@Column({ nullable: false, type: 'jsonb', default: [] })
publishedVersions: JsonbProperty<string[]>;
```
## `SerializedRelation`
A branded string type that marks foreign key IDs stored inside JSONB
objects. These are entity references serialized within a JSONB column
rather than being a regular database foreign key.
**Usage in jsonb property generic***
```ts
type FieldMetadataRelationSettings = {
relationType: RelationType;
onDelete?: RelationOnDeleteAction;
joinColumnName?: string | null;
junctionTargetFieldId?: SerializedRelation;
};
```
## `FormatJsonbSerializedRelation<T>`
A transformation type that processes JSONB properties for universal
entity mapping. It:
1. Detects properties with the `JsonbProperty` brand
2. Finds `SerializedRelation` properties
3. Renames them from `*Id` to `*UniversalIdentifier`
4. Removes the brand from the output type ( optional though )
```typescript
// Input: JsonbProperty<{ targetFieldMetadataId: SerializedRelation }>
// Output: { targetFieldMetadataUniversalIdentifier: SerializedRelation }
```
## Result
An example of the dynamic type mapping, through a type-test example
```ts
type SettingsTestCase = UniversalFlatFieldMetadata<
| FieldMetadataType.RELATION
| FieldMetadataType.NUMBER
| FieldMetadataType.TEXT
>['settings']
type SettingsExpectedResult =
| {
relationType: RelationType;
onDelete?: RelationOnDeleteAction | undefined;
joinColumnName?: string | null | undefined;
junctionTargetFieldUniversalIdentifier?: SerializedRelation | undefined;
}
| {
dataType?: NumberDataType | undefined;
decimals?: number | undefined;
type?: FieldNumberVariant | undefined;
}
| {
displayedMaxRows?: number | undefined;
}
| null;
type Assertions = [
Expect<Equal<SettingsTestCase, SettingsExpectedResult>>,
]
```
## Remarks
- Removed duplicated twenty-server and twenty-shared typed
- Removed class validator instances for default value that were not used
at runtime, we will refactor that to add validation across all entities
following a same pattern
# Introduction
Currently refactoring `flatEntity` typing, encountering some tsc errors
due to this fk aggregator custom override
It shall now follow generic pattern leading to be named `fieldIds`
Needs to flush object cache when released
fixes#16340
we are updating the cache partially to prevent the flow of the corrupted
fields into the cache
the fields get corrupted during the mutation process potentially
overriding the recoil cache as we are passing the `newRecordCache`
directly in `upsertRecordsInStore`, the newRecordCache is thin and hence
the recoil wipes out the fields that are undefined or empty
we prevent this by extracting the partial data ( only the data which is
being updated in the field ) and passing it to `upsertRecordsInStore`,
as it only touched the specific fields and updates the data, leaving the
other fields untouched.
https://github.com/user-attachments/assets/5256bef7-70c3-47b3-b2ce-dd02ee1a2de8
---------
Co-authored-by: Arun kumar <arunkumar@Aruns-MacBook-Air.local>
Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
# Introduction
A command allowing to invalidate flat cache entries. Extends the active
or suspended workspace coverage.
## Args
### --metadataName
If provided will invalidate metadata and related metadata cache entry (
can be repeated see usage )
### --all-metadata
Will invalidate all metadata entries
## Usage example
```
npx nx command twenty-server cache:flat-cache-invalidate --metadataName viewFilter --metadataName objectMetadata
```
```
npx nx command twenty-server cache:flat-cache-invalidate --all-metadata -w 0000-0000-0000-0000
```
## Summary
- **Fix junction relation toggle not being saved**: The form schema
wasn't tracking the `settings` field, so changes to
`junctionTargetFieldId` weren't marked as dirty
- **Add type-safe documentation paths**: Generate TypeScript constants
from `base-structure.json` to prevent broken documentation links
- **Create many-to-many relations documentation**: Step-by-step guide
for building many-to-many relations using junction objects
- **Update `getDocumentationUrl`**: Now uses shared constants from
`twenty-shared` for base URL, default path, and supported languages
## Key Changes
### Junction Toggle Fix
- Added `settings` field to the form schema in
`SettingsDataModelFieldRelationForm.tsx`
- Fixed the toggle to properly merge settings when updating
`junctionTargetFieldId`
### Type-Safe Documentation Paths
- New constants in `twenty-shared/constants`:
- `DOCUMENTATION_PATHS` - All 161 documentation paths as typed constants
- `DOCUMENTATION_SUPPORTED_LANGUAGES` - 14 supported languages
- `DOCUMENTATION_BASE_URL` / `DOCUMENTATION_DEFAULT_PATH`
- Generator script: `yarn docs:generate-paths`
- CI integration: Added to `docs-i18n-pull.yaml` workflow
### Documentation
- New article:
`/user-guide/data-model/how-tos/create-many-to-many-relations`
- Updated `/user-guide/data-model/capabilities/relation-fields.mdx` with
Lab warning and link
## Test plan
- [ ] Verify junction toggle saves correctly when enabled/disabled
- [ ] Verify documentation link opens correct localized page
- [ ] Verify `yarn docs:generate-paths` regenerates paths correctly
This fixes the #15896. For problem statement. Please refer to the shared
video mentioned in the issue.
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> Aligns filter defaults across UI by centralizing initial value
computation.
>
> - Add boolean handling in `useGetInitialFilterValue` to return
`value/displayValue` of `'false'` when operand is `IS`
> - Update `WorkflowDropdownStepOutputItems` to use
`getInitialFilterValue` for initial `value` instead of an empty string,
based on field type and default operand
>
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
3ce05fa31a. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
---------
Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
Co-authored-by: Félix Malfait <felix@twenty.com>
Closes [1744](https://github.com/twentyhq/core-team-issues/issues/1744).
This PR migrates attachments to morph relations behind a feature flag,
following the TimelineActivity pattern. it introduces the
`IS_ATTACHMENT_MIGRATED` flag, updates standard field metadata and
indexes to use morph relations, adds a workspace migration that renames
`attachment.*Id` columns to `target*Id` and converts the corresponding
field metadata to `MORPH_RELATION` with a shared `morphId`. On the
frontend, attachment read/write paths now switch to `target*Id` when the
flag is enabled.
It also fixes optimistic filtering for morph join columns. The metadata
API deduplicates morph fields, so attachments now expose a single target
field of type `MORPH_RELATION` plus a `morphRelations` array listing
each target object. Because only one `settings.joinColumnName` is
returned (e.g. `targetRocketId`), filters like `targetCompanyId` don’t
map to any field and the optimistic cache code throws.
`doesMorphRelationJoinColumnMatch` resolves this by computing all valid
join column names from `morphRelations` using
`computeMorphRelationFieldName` and comparing them to the filter key.
That makes filters like `targetCompanyId` resolvable even with a single
target field, so attachment uploads and list matching no longer crash.
<img width="477" height="474" alt="image"
src="https://github.com/user-attachments/assets/50e19418-3438-4d1e-9f1f-1bc1a03174a9"
/>
<br />
<br />
Today the metadata API returns one morph field called `target` and a
list of possible targets (`morphRelations`), but it does not tell us the
join column for each target. That’s why the Frontend had to compute join
column names.
If we want to fix this at the API level, there are two options:
- Add join column names to each target in `morphRelations` (e.g. company
→ `targetCompanyId`). This is additive and low‑risk.
- Return each target as its own field (`targetCompany`, `targetPerson`,
etc.) instead of a single target. This is a larger change because it
changes the shape of metadata and would require more UI updates.
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> Introduces morph relations for attachments behind
`IS_ATTACHMENT_MIGRATED`, aligning server schema/metadata and frontend
behavior.
>
> - Adds `IS_ATTACHMENT_MIGRATED` flag (frontend/server) and
seeds/defaults; updates generated GraphQL enums
> - New workspace upgrade `1.17` command migrates data: renames
`attachment.*Id` → `target*Id` and converts related fields to
`MORPH_RELATION` with shared `morphId`
> - Updates standard field metadata and indexes to `target*` (attachment
+ related objects), dev seeds, snapshots, and workspace entity types
> - Frontend: switches attachment read/write filters via
`getActivityTargetObjectFieldIdName` using the feature flag; updates
hooks/components (`useAttachments`, `useUploadAttachmentFile`, editors);
expands `Attachment` type
> - Fixes optimistic cache filtering to recognize morph join columns in
`isRecordMatchingFilter` by computing valid join-column keys from
`morphRelations`
>
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
f208fa23b1. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
---------
Co-authored-by: Félix Malfait <felix@twenty.com>
### Summary
The style guide currently discourages type imports, which contradicts
the enforced ESLint rule
`@typescript-eslint/consistent-type-imports` and existing code usage
(e.g. Storybook stories).
This PR updates the documentation to recommend inline type imports,
aligning the style guide
with tooling and preventing contributor confusion.
### Why
- Aligns documentation with enforced ESLint configuration
- Prevents ESLint auto-fix conflicts
- Improves contributor experience
Fixes #<### Summary
The style guide currently discourages type imports, which contradicts
the enforced ESLint rule
`@typescript-eslint/consistent-type-imports` and existing code usage
(e.g. Storybook stories).
This PR updates the documentation to recommend inline type imports,
aligning the style guide
with tooling and preventing contributor confusion.
### Why
- Aligns documentation with enforced ESLint configuration
- Prevents ESLint auto-fix conflicts
- Improves contributor experience
Fixes #<#17222>
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> Updates documentation to match tooling and current usage.
>
> - Replaces "Enforcing No-Type Imports" with **Type Imports**,
recommending inline type imports
> - Updates examples to prefer `import { type X } from ...` and avoid
importing types as runtime values
> - Clarifies ESLint `@typescript-eslint/consistent-type-imports`
configuration to prefer explicit type imports and enforce
`inline-type-imports` fix style
> - Changes confined to
`packages/twenty-docs/l/fr/developers/contribute/capabilities/frontend-development/style-guide.mdx`
>
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
e2250e43ad. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
Fixes#16388
Now we have used the metadata fetching from network using
resetVirtualizationBecauseDataChanged(), as we navigate back to the
table after updating the field content. As the virtualised table uses
the "cache-first" strategy, it fails to give fresh data from the data
base
Please let me know i we need to add the loading state (and it's UI
requirements) while it fetches the cell content, as currently the cell
is empty until it's populated by fresh data
https://github.com/user-attachments/assets/901085ba-4337-4a5d-86c8-15ac84250e8f
---------
Co-authored-by: Arun kumar <arunkumar@Aruns-MacBook-Air.local>
Co-authored-by: Lucas Bordeau <bordeau.lucas@gmail.com>
## Summary
- The `createMany` API was returning records in arbitrary order because
`fetchUpsertedRecords` used `WHERE id IN (...)` without `ORDER BY`
- This caused flaky tests that assumed input order was preserved (e.g.,
`people-merge-many.integration-spec.ts`)
- Fixed by reordering the fetched records to match the original input
order from `generatedMaps`
## Root Cause
```typescript
// Before: No ORDER BY, so SQL returns in arbitrary order
const upsertedRecords = await queryBuilder
.where({ id: In(objectRecords.generatedMaps.map((record) => record.id)) })
.getMany(); // Returns in arbitrary order!
```
## Fix
```typescript
// After: Reorder results to match original input order
const orderedIds = objectRecords.generatedMaps.map((record) => record.id);
const upsertedRecords = await queryBuilder
.where({ id: In(orderedIds) })
.getMany();
// Preserve original input order
const recordsById = new Map(upsertedRecords.map((record) => [record.id, record]));
return orderedIds
.map((id) => recordsById.get(id))
.filter((record) => record !== undefined);
```
## Test plan
- [x] Run `people-merge-many.integration-spec.ts` multiple times -
passes consistently
- [x] Lint passes
## Summary
- Adds SSRF (Server-Side Request Forgery) protection to webhook requests
by using the same secure axios adapter already used by HTTP workflow
actions
- Prevents webhooks from making requests to private/internal IP
addresses (10.x, 192.168.x, 172.16-31.x, 169.254.x, localhost)
- Adds specific error logging when a webhook fails due to SSRF
protection
## Context
The HTTP workflow tool (`HTTP_REQUEST` action) already had SSRF
protection via `HTTP_TOOL_SAFE_MODE_ENABLED`, but webhooks were using
`HttpService` directly without this protection. This inconsistency meant
users could potentially configure webhooks to probe internal
infrastructure.
### What's protected now:
| Feature | Before | After |
|---------|--------|-------|
| HTTP Workflow Action | Protected (secure adapter) | Protected (secure
adapter) |
| Webhooks | **Unprotected** | Protected (secure adapter) |
### The secure adapter validates:
1. Protocol must be `http:` or `https:`
2. DNS resolution of hostname
3. Resolved IP must not be in private ranges
## Test plan
- [ ] Configure a webhook with an external URL (e.g.,
`https://webhook.site`) - should work
- [ ] Configure a webhook with `http://localhost:3000` - should fail
with SSRF error in audit log
- [ ] Configure a webhook with `http://10.0.0.1/test` - should fail with
SSRF error in audit log
- [ ] Configure a webhook with a domain that resolves to a private IP -
should fail
This PR modifies our broadly used `formatResult` util to counter act
TypeORM transforming any date time to a `Date` object.
Instead we return the ISO string for any `DATE_TIME`, this way we're not
transporting Date object from one function to another in the backend.
We do this because there was problems working with events utils that
take string date time in parameters and received Date objects.
As this is a recurring problem and because it's an opinionated choice
from TypeORM, we chose to switch to string only in our codebase, from
TypeORM's output to frontend.
This PR changes the shape and logic of SSE events `DELETE` and
`RESTORE`, because they behave like `UPDATE` events in practice, they
should share the same logic.
Before this PR, it was impossible for the frontend to obtain the
`deletedAt` value, and the logic to handle soft-delete and restore would
have been flawed.
Because there is a typing confusion in the parameters of
`formatTwentyOrmEventToDatabaseBatchEvent`, due to TypeORM, we also
update this util to only accept an array of records, instead of `T |
T[]`. We should improve our TypeORM layer in the future.
Also the naming was not clear, so we clearly use `recordsAfter` and
`recordsBefore` as much as possible, because that is what we have at the
end in events.
Events are sent from their respective query builders, so these last ones
have been updated also.
Because TypeORM `soft-remove` operation only returns record ids, we add
`.getMany()` to fetch all fields for soft-removed records, so that our
event can have before and after.
## 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
# Introduction
Creating a `UniversalFlatEntityFrom` that strips out all the relation
and foreignKey properties in order to replace them with
`UniversalIdentifier` suffix
This data type will be major for the workspace migration workspace
agnostic refactor
## Chore
- renamed `flat-entity.type` to `flat-entity-from.type.ts` ( more
accurate to exported module )
- create static test type over the field metadata entity on quite
complex utils as both coverage and documentation
## Example
Here's an example of a `UniversalFlatEntityFrom<FieldMetadataEntity>`
```ts
const universalFlatFieldMetadata: UniversalFlatFieldMetadata<FieldMetadataType.RELATION> = {
// Base properties (from FieldMetadataEntity, excluding relations and applicationId)
universalIdentifier: '550e8400-e29b-41d4-a716-446655440001',
applicationUniversalIdentifier: '5800681c-088e-4e2b-9fc3-bcf6e8ec2051',
type: FieldMetadataType.RELATION,
name: 'firstName',
label: 'First Name',
defaultValue: null,
description: 'The first name of the person',
icon: 'IconUser',
standardOverrides: null,
options: null,
settings: {
relationType: RelationType.ONE_TO_MANY,
},
isCustom: false,
isActive: true,
isSystem: false,
isUIReadOnly: false,
isNullable: true,
isUnique: false,
isLabelSyncedWithName: true,
morphId: null,
// Date properties cast to string
createdAt: '2024-01-15T10:30:00.000Z',
updatedAt: '2024-01-15T10:30:00.000Z',
// ManyToOne relation universal identifiers (from FieldMetadataEntity relations)
relationTargetFieldMetadataUniversalIdentifier:
'550e8400-e29b-41d4-a716-446655440012',
relationTargetObjectMetadataUniversalIdentifier:
'550e8400-e29b-41d4-a716-446655440013',
// Join column universal identifiers (foreignKey -> universalIdentifier)
objectMetadataUniversalIdentifier: '550e8400-e29b-41d4-a716-446655440010',
// OneToMany relation universal identifiers (array of related entity identifiers)
viewFieldUniversalIdentifiers: [
'550e8400-e29b-41d4-a716-446655440020',
'550e8400-e29b-41d4-a716-446655440021',
],
viewFilterUniversalIdentifiers: ['550e8400-e29b-41d4-a716-446655440030'],
kanbanAggregateOperationViewUniversalIdentifiers: [],
calendarViewUniversalIdentifiers: [],
mainGroupByFieldMetadataViewUniversalIdentifiers: [],
};
```
## Settings
Will hop on the settings typing next. Might not be dynamic but
declarative though
## Refactor app-dev state management and build utilities
- Store only `manifest` in `AppDevState` instead of full
`ManifestBuildResult`; add `sourcePath` to `FileStatus`
- Pass `sourcePaths` directly to watchers instead of
`ManifestBuildResult`
- Only reset `fileUploadStatus` for functions/components when their
source paths change
- Add pure `updateManifestChecksum` utility that returns a new manifest
without side effects
- Extract `processEsbuildResult` to deduplicate build result processing
between watchers
- Rename `serverlessFunctions` → `functions` in SDK code (API unchanged)
- Extract `writeManifestToOutput` to shared `manifest-writer.ts`
Until now, it wasn’t possible to navigate between page layouts.
Dashboards don’t contain links to other dashboards. With record page
layouts, however, a page layout can now contain a link to another page
layout. If the user opens a record in the side panel and then clicks a
link to another record, the current page layout is replaced with the
page layout for the clicked record.
In this scenario, the `PageLayoutRenderer` component is not remounted.
As a result, the `isInitialized` state was not reset to `false`, and the
corresponding initialization `useEffect` was not triggered again.
All page layout states are component states bound to
`PageLayoutComponentInstanceContext`. For example, after navigating to
another record, `pageLayoutPersistedComponentState` was actually
`undefined` because the context's `instanceId` had changed.
Replacing the `isInitialized` state with a component state fixes the bug
and is consistent with the existing pattern of using component states to
store everything related to page layouts.
## Before
https://github.com/user-attachments/assets/24217145-51e5-49ef-8180-de62a6acfe10
## After
https://github.com/user-attachments/assets/e0ffe107-8fae-4f3a-9016-72c85bc735f4
Previously we introduced useUpdateOneRecordV2 to handle morph relations.
In this PR we are removing it to only use useUpdateOneRecord which has
been adapted not to requires objectMetadataNameSingular in its props.
Fixes
https://github.com/twentyhq/private-issues/issues/410#issuecomment-3781085655
Currently, JSON keys with spaces like { "toto toto": 123 } are rejected
with "JSON keys cannot contain spaces" error. This is problematic for
HTTP requests and webhook triggers where users cannot control the
response structure.
We use Handlebars to eval variables, segment-literal bracket notation to
escape keys with special characters:
Normal: {{step.normalKey}}
With spaces: `{{step.[key with space]}}`
So we simply need to wrap segments with spaces with brackets.
This PR:
- Create shared path utilities to wrap the variable segments when needed
- Use it in all variable generation places
- Remove the restrictions
This body is now supported:
<img width="609" height="457" alt="Capture d’écran 2026-01-22 à 16 10
13"
src="https://github.com/user-attachments/assets/e7653c0a-df1e-49af-9c9a-4b7d59a99726"
/>
## Context
Removing full CRUD to RLS since we are actually using an upsert only
over the role resolver, this removes a lot of unused code (could be
re-added later but unlikely). Simplified the folder arch at the same
time
Add simple integration tests to RLS
**Summary**
Issue #16963: When an object is renamed, the morph relation picker still
shows the old name.
**Root cause**
The picker used searchRecord.objectNameSingular from the GraphQL search
response, which can be stale after a rename.
The search record stores the object name at query time, not the current
metadata.
**Solution**
- Updated SingleRecordPickerMenuItem:
- Added useObjectMetadataItems to access current object metadata.
- Look up the current object metadata by morphItem.objectMetadataId.
- Use labelSingular (or nameSingular as fallback) instead of
searchRecord.objectNameSingular for display.
- Updated MultipleRecordPickerMenuItemContent:
- Use objectMetadataItem.labelSingular (already available as a prop)
instead of searchRecord.objectNameSingular.
---------
Co-authored-by: Marie Stoppa <marie.stoppa@essec.edu>
# Introduction
In this PR we catch all the runner errors coming from a single workspace
migration action execution.
**1. `WorkspaceMigrationActionExecutionException`** (low-level,
action-specific)
- Thrown from action handlers, utils, and helper functions
- Contains specific error codes: `FIELD_METADATA_NOT_FOUND`,
`OBJECT_METADATA_NOT_FOUND`, `ENUM_OPERATION_FAILED`, `NOT_SUPPORTED`,
etc.
- Simple structure: `message`, `code`, `userFriendlyMessage`
- No action context - just describes what went wrong
**2. `WorkspaceMigrationRunnerException`** (high-level, runner-scoped)
- Only two codes: `INTERNAL_SERVER_ERROR` and `EXECUTION_FAILED`
- `EXECUTION_FAILED` **requires** `action` + `errors` (contains the
action context)
- `INTERNAL_SERVER_ERROR` **requires** `message` (no action context)
## Refactor
- Removed the `relatedFlatEntityMapsKeys` from the `WorkspaceMigration`
type as they're directly inferred from passed actions
- Swallowing actions rollbacks errors in order to iterate over all of
them
## Testing
Created a very straigthforward install application from workspace
migration endpoint in order to start testing the introduced
`WorkspaceMigrationActionExecutionException`
Introduced a feature flag that stop the access to the endpoint if not
enabled
Whole taken direction are totally subjective and highly prone to
mutations ( endpoint location, naming and input schema see
`ts-expect-error` comment )
cc @martmull
## Response error
```ts
{
"eventId": "evt_a1b2c3d4-5678-90ab-cdef-1234567890ab",
"extensions": {
"action": {
"metadataName": "fieldMetadata",
"type": "delete",
"universalIdentifier": "20202020-6110-4547-9fd0-2525257a2c3f"
},
"code": "APPLICATION_INSTALLATION_FAILED",
"errors": {
"metadata": {
"code": "ENTITY_NOT_FOUND",
"message": "Could not find flat entity with universal identifier 20202020-6110-4547-9fd0-2525257a2c3f"
},
"workspaceSchema": {
"code": "ENTITY_NOT_FOUND",
"message": "Could not find flat entity in maps"
}
},
"exceptionEventId": "exc_f9e8d7c6-5432-10ba-fedc-ba0987654321",
"userFriendlyMessage": "Migration execution failed."
},
"message": "Migration action 'delete' for 'fieldMetadata' failed",
"name": "GraphQLError"
}
```
[](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 merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@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>
Bumps [ts-key-enum](https://gitlab.com/nfriend/ts-key-enum) from 2.0.12
to 2.0.13.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://gitlab.com/nfriend/ts-key-enum/tags">ts-key-enum's
releases</a>.</em></p>
<blockquote>
<h2>v2.0.13</h2>
<p>Remove link to Wikipedia page on teletext.</p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://gitlab.com/nfriend/ts-key-enum/commit/e9259b2365bbcd82abfb3cfa38a1f1a78041e95a"><code>e9259b2</code></a>
2.0.13</li>
<li><a
href="https://gitlab.com/nfriend/ts-key-enum/commit/5f8a0e079164cad933f1481034d063e088c2a5a3"><code>5f8a0e0</code></a>
MDN update</li>
<li>See full diff in <a
href="https://gitlab.com/nfriend/ts-key-enum/compare/v2.0.12...v2.0.13">compare
view</a></li>
</ul>
</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 merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after
your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge
and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating
it. You can achieve the same result by closing it manually
- `@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>
This PR update the redis subscription iterator wrapper with an heartbeat
system. Heartbeat interval are based on event stream TTL. Those refresh
the event stream TTL and active streams in redis.
I also cleaned a few functions that were not used anymore.
---------
Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
## Summary
- Adds `exclude-paths` configuration to Dependabot to skip
`packages/twenty-apps/community/**`
- Community-maintained apps have their own dependency management and
don't need the same security requirements as core packages
## Test plan
- Verify Dependabot no longer creates alerts/PRs for dependencies in
community apps
## Summary
Adds `app:build` command as a one-shot version of `app:dev` - builds
manifest, functions, and front components once then exits (no watching).
**Changes:**
- Added `watch` option to `FunctionsWatcher` and
`FrontComponentsWatcher` to support both watch and one-shot modes
- Created `AppBuildCommand` reusing the same build logic as `app:dev`
with `watch: false`
- Added integration tests for `app:build` on both `rich-app` and
`root-app`
- Updated manifest types: `handlerPath` → `sourceHandlerPath` +
`builtHandlerPath`, `componentPath` → `sourceComponentPath` +
`builtComponentPath`
- Updated test app `package.json` scripts to match `create-twenty-app`
template
Implements a new syncable `navigationMenuItem` entity in the core schema
to replace the workspace `favorite` entity.
## Next Steps
- Frontend integration ([separate
PR](https://github.com/twentyhq/twenty/pull/17268))
- Data migration (separate PR)
Summary
- Serverless functions are now built when created/updated instead of at
execution time
- Added builtHandlerPath field to track pre-built function artifacts
- Renamed base-typescript-project to seed-project with pre-built ESM
output included
- Renamed file folder enums for consistency (Functions → BuiltFunction,
SourceCode → Source)
- supports backwards compatibility with existing serverless functions
Tested with all combinations
- storage : local driver and S3 driver
- serverless : local driver and lambda driver
Gmail emails often have multiple labels - an email in "XYZ" label
typically also has "INBOX". The previous negative filter approach
(-label:inbox -label:sent...) excluded these emails entirely, even when
they had a selected label.
Switched to positive OR filtering: (label:cyz OR label:xyz-visible)
-label:spam... which correctly fetches emails with any of the selected
labels regardless of other labels they have.
This edge case wasn't caught earlier because manual testing with INBOX
selected worked fine - it only surfaced when selecting custom labels
without INBOX.
# Introduction
Created a task that allow spawning a term that will run the opened file
and run its integration tests
## Motivation
Jest extension is quite buggy lately, this might just be a tmp
workaround but we can't get `run test` within the test file directly.
Also passing by task allows giving inputs
## Usage
`cmd+shift+P`-> `run task` -> `twenty server - run integration test
file` -> fill inputs prompts:
- watch: will rerun on every related files updates
- updateSnapshot
Note: you can add a custom shortcut to the task too, also it will appear
in task history
Watch mode won't re-create the server instance on each run, meaning that
nest won't hit the `createServer` making very fast to iterate with
Down side of this is that any module injection won't get hydrated
Refactor manifest build and remove src folder assumptions
- Unified entity builder interface: All entity builders now return
EntityBuildResult<T> with both manifests and filePaths
- Always return build result: runManifestBuild now always returns {
manifest, filePaths } instead of null on failure
- Return all entity paths: EntityFilePaths includes paths for all entity
types (application, objects, functions, etc.)
- Remove src folder assumptions: Applications can now have entities at
root level - removed hasSrcFolder checks
- Simplify watchers: Removed entries caching, compute lazily with map()
- Stabilize tests: Replaced flaky console output snapshots with key
message assertions; replaced inline snapshots with array comparisons
-
Fixes https://github.com/twentyhq/core-team-issues/issues/1956
**Problem**
Within an app, the `.yarn/releases/` folder contains executable Yarn
binaries that run when executing any yarn command (`.yarnrc` file
indicates yarn path to be `.yarn/releases/yarn-4.9.2.cjs `.)
This is a supply chain attack vector: a malicious actor could submit a
PR with a compromised `yarn-4.9.2.cjs binary`, which would execute
arbitrary code on developers' machines or CI systems.
**Fix**
Actually, thanks to Corepack, we don't need to store and execute this
binary.
Corepack can be seen as the manager of a package manager: in
`package.json` we indicate a packageManager version like
`"packageManager": "yarn@4.9.2"`, and when executing `yarn` Corepack
will securely fetch the verified version from npm, avoiding the risk of
executing a compromised binary committed to the repository. This was
already in our app's package.json template but we were not using it!
We can now
- remove the folder containing the binary from our app template
base-application (that is scaffolded when creating an app through cli),
`.yarn/releases/`, and remove `yarnPath: .yarn/releases/yarn-4.9.2.cjs`
from its .yarnrc
- remove them from the community apps that were already published in the
repo
- add .yarn to gitignore
**Tested**
This has been tested and works for app created in the repo, outside the
repo, and existing apps in the repo
# Introduction
following https://github.com/twentyhq/twenty/pull/17279
As we've finally identified all the syncable metadata entities, which
means they're expected to have non nullable applicationId and
universalIdentifier at pg_level we can remove previous retro comp
universalIdentifier fallbacking and update the dto too
~~This needs IdentifyRemainingEntitiesMetadataCommand and
MakeRemainingEntitiesUniversalIdentifierAndApplicationIdNotNullableMigrationCommand
to be run~~
```ts
[Nest] 197 - 01/21/2026, 3:08:35 PM LOG [MakeRemainingEntitiesUniversalIdentifierAndApplicationIdNotNullableMigrationCommand] Successfully run MakeRemainingEntitiesUniversalIdentifierAndApplicationIdNotNullableMigrationCommand
```
Fixes#17253
When clicking 'No {Field}' in a polymorphic relation picker, the
relation was not being cleared. This commit implements the missing
detach logic:
- RecordDetailMorphRelationSectionDropdownManyToOne: Call onSubmit with
newValue: null when no item is selected
- MorphRelationManyToOneFieldInput: Call persistMorphManyToOne with
valueToPersist: null for detach case
- useMorphPersistManyToOne: Implement actual detach logic that sets all
morph relation ID fields to null via updateOneRecord
Also adds unit tests for the useMorphPersistManyToOne hook.
description:Create validation logic and migration action builders for syncable entities in Twenty. Use when implementing business rule validation, uniqueness checks, foreign key validation, or building workspace migration actions for syncable entities. Validators never throw and never mutate.
description:Create cache services and transformation utilities for syncable entities in Twenty. Use when implementing entity-to-flat conversions, input DTO transpilation to universal flat entities, or cache recomputation for syncable entities.
---
# Syncable Entity: Cache & Transform (Step 2/6)
**Purpose**: Create cache layer and transformation utilities to convert between different entity representations.
**When to use**: After completing Step 1 (Types & Constants). Required before building validators and action handlers.
description:Wire syncable entity services into NestJS modules, create service layer and resolvers for Twenty entities. Use when registering builders, validators, and action handlers in modules, creating business services, or exposing entities via GraphQL API with proper exception handling.
---
# Syncable Entity: Integration (Step 5/6)
**Purpose**: Wire everything together, register in modules, create services and resolvers.
**When to use**: After completing Steps 1-4 (all previous steps). Required before testing.
description:Implement action handlers for executing workspace migrations in Twenty. Use when creating database operations for syncable entities, implementing universal-to-flat entity transpilation, or handling create/update/delete actions in the runner layer.
---
# Syncable Entity: Runner & Actions (Step 4/6)
**Purpose**: Execute migration actions against the database with proper transpilation from universal to flat entities.
**When to use**: After completing Steps 1-3 (Types, Cache, Builder). Required before integration.
description:Create comprehensive integration tests for syncable entities in Twenty. Use when writing integration tests for metadata entities, covering validator exceptions, input transpilation errors, and CRUD operations. Tests are MANDATORY for all syncable entities.
description:Define types, entities, and central constant registrations for syncable entities in Twenty's workspace migration system. Use when creating new syncable entities, defining TypeORM entities, flat entity types, or registering in central constants (ALL_ENTITY_PROPERTIES_CONFIGURATION_BY_METADATA_NAME, ALL_ONE_TO_MANY_METADATA_RELATIONS, ALL_MANY_TO_ONE_METADATA_FOREIGN_KEY, ALL_MANY_TO_ONE_METADATA_RELATIONS).
---
# Syncable Entity: Types & Constants (Step 1/6)
**Purpose**: Define all types, entities, and register in central constants. This is the foundation - everything else depends on these types being correct.
**When to use**: First step when creating any new syncable entity. Must be completed before other steps.
This constant is **type-checked** — values for `metadataName`, `flatEntityForeignKeyAggregator`, and `universalFlatEntityForeignKeyAggregator` are derived from entity type definitions. The aggregator names follow the pattern: remove trailing `'s'` from the relation property name, then append `Ids` or `UniversalIdentifiers`.
```typescript
exportconstALL_ONE_TO_MANY_METADATA_RELATIONS={
// ... existing entries
myEntity:{
// If myEntity has a `childEntities: ChildEntityEntity[]` property:
Low-level primitive constant. Only contains `foreignKey` — the column name ending in `Id` that stores the foreign key. Type-checked against entity properties.
Derived from both `ALL_MANY_TO_ONE_METADATA_FOREIGN_KEY` (for `foreignKey` type and `universalForeignKey` derivation) and `ALL_ONE_TO_MANY_METADATA_RELATIONS` (for `inverseOneToManyProperty` key constraint). This is the main constant consumed by utils and optimistic tooling.
```typescript
exportconstALL_MANY_TO_ONE_METADATA_RELATIONS={
// ... existing entries
myEntity:{
workspace: null,
application: null,
parentEntity:{
metadataName:'parentEntity',
foreignKey:'parentEntityId',
inverseOneToManyProperty:'myEntities',// key in ALL_ONE_TO_MANY_METADATA_RELATIONS['parentEntity'], or null if no inverse
-`inverseOneToManyProperty` — must be a key in `ALL_ONE_TO_MANY_METADATA_RELATIONS[targetMetadataName]`, or `null` if the target entity doesn't expose an inverse one-to-many relation
-`universalForeignKey` — derived from `foreignKey` by replacing the `Id` suffix with `UniversalIdentifier`
- Optimistic utils resolve `flatEntityForeignKeyAggregator` / `universalFlatEntityForeignKeyAggregator` at runtime by looking up `inverseOneToManyProperty` in `ALL_ONE_TO_MANY_METADATA_RELATIONS`
---
## Checklist
Before moving to Step 2:
- [ ] Metadata name added to `ALL_METADATA_NAME`
- [ ] TypeORM entity created (extends `SyncableEntity`)
- [ ]`isCustom` column added
- [ ] Flat entity type defined
- [ ] Flat entity maps type defined (if needed)
- [ ] Editable properties constant defined
- [ ] Universal and flat action types defined
- [ ] Registered in `AllFlatEntityTypesByMetadataName`
- [ ] Registered in `ALL_ENTITY_PROPERTIES_CONFIGURATION_BY_METADATA_NAME`
- [ ] Registered in `ALL_ONE_TO_MANY_METADATA_RELATIONS` (if entity has one-to-many relations)
- [ ] Registered in `ALL_MANY_TO_ONE_METADATA_FOREIGN_KEY`
- [ ] Registered in `ALL_MANY_TO_ONE_METADATA_RELATIONS`
- [ ] TypeScript compiles without errors
---
## Next Step
Once all types and constants are defined, proceed to:
BODY="⚠️ Claude ran out of turns before creating a PR. Work has been pushed to [\`$BRANCH\`](https://github.com/${{ github.repository }}/tree/$ENCODED_BRANCH).\n\n[**Create PR →**]($PR_URL)"
prompt = `You are responding to a comment on issue #${p.issue_number} ("${p.issue_title}") in the ${p.repo_full_name} repository.\n\nThe comment by @${p.sender} says:\n\n${p.comment_body}\n\nIssue body:\n\n${p.issue_body}\n\nPlease help with this request. The code you are working with is the twenty codebase (this repository).`;
} else {
prompt = `You are responding to issue #${p.issue_number} ("${p.issue_title}") in the ${p.repo_full_name} repository, opened by @${p.sender}.\n\nIssue body:\n\n${p.issue_body}\n\nPlease help with this request. The code you are working with is the twenty codebase (this repository).`;
console.error('[NX]: The "installation" entry in the "nx.json" file is required when running the nx wrapper. See https://nx.dev/recipes/installation/install-non-javascript');
process.exit(1);
}
}
catch{
console.error('[NX]: The "nx.json" file is required when running the nx wrapper. See https://nx.dev/recipes/installation/install-non-javascript');
IMPORTANT: Use Context7 for code generation, setup or configuration steps, or library/API documentation. Automatically use the Context7 MCP tools to resolve library IDs and get library docs without waiting for explicit requests.
### Before Making Changes
1. Always run linting and type checking after code changes
2. Test changes with relevant test suites
3. Ensure database migrations are properly structured
1. Always run linting (`lint:diff-with-main`) and type checking after code changes
2. Test changes with relevant test suites (prefer single-file test runs)
3. Ensure database migrations are generated for entity changes
4. Check that GraphQL schema changes are backward compatible
5. Run `graphql:generate` after any GraphQL schema changes
### Code Style Notes
- Use **Emotion** for styling with styled-components pattern
- Follow **Nx** workspace conventions for imports
- Use **Lingui** for internationalization
-Components should be in their own directories with tests and stories
-Apply security first, then formatting (sanitize before format)
### Testing Strategy
- **Unit tests** with Jest for both frontend and backend
- **Integration tests** for critical backend workflows
-**Storybook** for component development and testing
-**E2E tests** with Playwright for critical user flows
- **Test behavior, not implementation** — focus on user perspective
- **Test pyramid**: 70% unit, 20% integration, 10% E2E
-Query by user-visible elements (text, roles, labels) over test IDs
-Use `@testing-library/user-event` for realistic interactions
- Descriptive test names: "should [behavior] when [condition]"
- Clear mocks between tests with `jest.clearAllMocks()`
## CI Environment (GitHub Actions)
When running in CI, the dev environment is **not** pre-configured. Dependencies are installed but builds, env files, and databases are not set up.
- **Before running tests, builds, lint, type checks, or DB operations**, run: `bash packages/twenty-utils/setup-dev-env.sh`
- **Skip the setup script** for tasks that only read code — architecture questions, code review, documentation, etc.
- The script is idempotent and safe to run multiple times.
## Important Files
-`nx.json` - Nx workspace configuration with task definitions
-`tsconfig.base.json` - Base TypeScript configuration
-`package.json` - Root package with workspace definitions
-`.cursor/rules/` - Development guidelines and best practices
-`.cursor/rules/` - Detailed development guidelines and best practices
Create Twenty App is the official scaffolding CLI for building apps on top of [Twenty CRM](https://twenty.com). It sets up a ready‑to‑run project that works seamlessly with the [twenty-sdk](https://www.npmjs.com/package/twenty-sdk).
- Zero‑config project bootstrap
- Preconfigured scripts for auth, generate, dev sync, one‑off sync, uninstall
- Preconfigured scripts for auth, dev mode (watch & sync), uninstall, and function management
- Strong TypeScript support and typed client generation
## Documentation
See Twenty application documentation https://docs.twenty.com/developers/extend/capabilities/apps
## Prerequisites
- Node.js 24+ (recommended) and Yarn 4
- A Twenty workspace and an API key (create one at https://app.twenty.com/settings/api-webhooks)
@@ -28,41 +31,90 @@ Create Twenty App is the official scaffolding CLI for building apps on top of [T
npx create-twenty-app@latest my-twenty-app
cd my-twenty-app
# Get Help
yarn run help
# If you don't use yarn@4
corepack enable
yarn install
# Get help and list all available commands
yarn twenty help
# Authenticate using your API key (you'll be prompted)
yarn auth:login
yarn twenty auth:login
# Add a new entity to your application (guided)
yarn entity:add
yarn twenty entity:add
# Generate a typed Twenty client and workspace entity types
yarn app:generate
# Start dev mode: watches, builds, and syncs local changes to your workspace
# (also auto-generates a typed API client in node_modules/twenty-sdk/generated)
yarn twenty app:dev
# Start dev mode: automatically syncs local changes to your workspace
- A prewired `twenty` script that delegates to the `twenty` CLI from twenty-sdk
**Example files (controlled by scaffolding mode):**
-`objects/example-object.ts` — Example custom object with a text field
-`fields/example-field.ts` — Example standalone field extending the example object
-`logic-functions/hello-world.ts` — Example logic function with HTTP trigger
-`front-components/hello-world.tsx` — Example front component
-`views/example-view.ts` — Example saved view for the example object
-`navigation-menu-items/example-navigation-menu-item.ts` — Example sidebar navigation link
-`skills/example-skill.ts` — Example AI agent skill definition
## Next steps
-Explore the generated project and add your first entity with `yarn entity:add`.
-Keep your types up‑to‑date using `yarn app:generate`.
-Use `yarn app:dev` while you iterate to see changes instantly in your workspace.
-Run `yarn twenty help` to see all available commands.
-Use `yarn twenty auth:login` to authenticate with your Twenty workspace.
-Explore the generated project and add your first entity with `yarn twenty entity:add` (logic functions, front components, objects, roles, views, navigation menu items, skills).
- Use `yarn twenty app:dev` while you iterate — it watches, builds, and syncs changes to your workspace in real time.
- Types are auto‑generated by `yarn twenty app:dev` and stored in `node_modules/twenty-sdk/generated`.
## Publish your application
@@ -90,8 +142,8 @@ git push
Our team reviews contributions for quality, security, and reusability before merging.
## Troubleshooting
- Auth prompts not appearing: run `yarn auth:login` again and verify the API key permissions.
- Types not generated: ensure `yarn app:generate` runs without errors, then re‑start `yarn app:dev`.
- Auth prompts not appearing: run `yarn twenty auth:login` again and verify the API key permissions.
- Types not generated: ensure `yarn twenty app:dev` is running — it auto‑generates the typed client.
## Contributing
- See our [GitHub](https://github.com/twentyhq/twenty)
"SERVER_URL":{"type":"string","description":"Override for the server URL (e.g., https://crm.example.com). If not set, derived from ingress or service."},
@@ -9,16 +9,14 @@ Apps are currently in alpha testing. The feature is functional but still evolvin
## What Are Apps?
Apps let you build and manage Twenty customizations **as code**. Instead of configuring everything through the UI, you define your data model and serverless functions in code — making it faster to build, maintain, and roll out to multiple workspaces.
Apps let you build and manage Twenty customizations **as code**. Instead of configuring everything through the UI, you define your data model and logic functions in code — making it faster to build, maintain, and roll out to multiple workspaces.
**What you can do today:**
- Define custom objects and fields as code (managed data model)
- Build serverless functions with custom triggers
- Build logic functions with custom triggers
- Define skills for AI agents
- Deploy the same app across multiple workspaces
**Coming soon:**
- Custom UI layouts and components
## Prerequisites
- Node.js 24+ and Yarn 4
@@ -29,40 +27,54 @@ Apps let you build and manage Twenty customizations **as code**. Instead of conf
Create a new app using the official scaffolder, then authenticate and start developing:
```bash filename="Terminal"
# Scaffold a new app
# Scaffold a new app (includes all examples by default)
npx create-twenty-app@latest my-twenty-app
cd my-twenty-app
# If you don't use yarn@4
corepack enable
yarn install
# Authenticate using your API key (you'll be prompted)
yarn auth:login
yarn twenty auth:login
# Start dev mode: automatically syncs local changes to your workspace
yarn app:dev
yarn twenty app:dev
```
The scaffolder supports three modes for controlling which example files are included:
```bash filename="Terminal"
# Default (exhaustive): all examples (object, field, logic function, front component, view, navigation menu item, skill)
npx create-twenty-app@latest my-app
# Minimal: only core files (application-config.ts and default-role.ts)
npx create-twenty-app@latest my-app --minimal
# Interactive: select which examples to include
npx create-twenty-app@latest my-app --interactive
```
From here you can:
```bash filename="Terminal"
# Add a new entity to your application (guided)
yarn app:create-entity
yarn twenty entity:add
# Generate a typed Twenty client and workspace entity types
# Uninstall the application from the current workspace
yarn app:uninstall
yarn twenty app:uninstall
# Display commands' help
yarn app:help
yarn twenty help
```
See also: the CLI reference pages for [create-twenty-app](https://www.npmjs.com/package/create-twenty-app) and [twenty-sdk CLI](https://www.npmjs.com/package/twenty-sdk).
@@ -74,9 +86,9 @@ When you run `npx create-twenty-app@latest my-twenty-app`, the scaffolder:
- Copies a minimal base application into `my-twenty-app/`
- Adds a local `twenty-sdk` dependency and Yarn 4 configuration
- Creates config files and scripts wired to the `twenty` CLI
- Generates a default application config and a default function role
- Generates core files (application config, default function role, post-install function) plus example files based on the scaffolding mode
A freshly scaffolded app looks like this:
A freshly scaffolded app with the default `--exhaustive` mode looks like this:
```text filename="my-twenty-app/"
my-twenty-app/
@@ -86,89 +98,84 @@ my-twenty-app/
.nvmrc
.yarnrc.yml
.yarn/
releases/
yarn-4.9.2.cjs
install-state.gz
eslint.config.mjs
tsconfig.json
README.md
public/ # Public assets folder (images, fonts, etc.)
src/
app/
application.config.ts # Required - main application configuration
default-function.role.ts # Default role for serverless functions
// your entities (*.object.ts, *.function.ts, *.role.ts)
├── application-config.ts # Required - main application configuration
├── roles/
│└── default-role.ts # Default role for logic functions
├── objects/
│ └── example-object.ts # Example custom object definition
├── fields/
│ └── example-field.ts # Example standalone field definition
├── logic-functions/
│ ├── hello-world.ts # Example logic function
│ └── post-install.ts # Post-install logic function
├── front-components/
│ └── hello-world.tsx # Example front component
├── views/
│ └── example-view.ts # Example saved view definition
├── navigation-menu-items/
│ └── example-navigation-menu-item.ts # Example sidebar navigation link
└── skills/
└── example-skill.ts # Example AI agent skill definition
```
### Convention-over-configuration
Applications use a **convention-over-configuration** approach where entities are detected by their file suffix. This allows flexible organization within the `src/app/` folder:
| File suffix | Entity type |
|-------------|-------------|
| `*.object.ts` | Custom object definitions |
| `*.function.ts` | Serverless function definitions |
| `*.role.ts` | Role definitions |
### Supported folder organizations
You can organize your entities in any of these patterns:
**Traditional (by type):**
```text
src/app/
├── application.config.ts
├── objects/
│ └── postCard.object.ts
├── functions/
│ └── createPostCard.function.ts
└── roles/
└── admin.role.ts
```
**Feature-based:**
```text
src/app/
├── application.config.ts
└── post-card/
├── postCard.object.ts
├── createPostCard.function.ts
└── postCardAdmin.role.ts
```
**Flat:**
```text
src/app/
├── application.config.ts
├── postCard.object.ts
├── createPostCard.function.ts
└── admin.role.ts
```
With `--minimal`, only the core files are created (`application-config.ts`, `roles/default-role.ts`, and `logic-functions/post-install.ts`). With `--interactive`, you choose which example files to include.
At a high level:
- **package.json**: Declares the app name, version, engines (Node 24+, Yarn 4), and adds `twenty-sdk` plus scripts like `dev`, `sync`, `generate`, `create-entity`, `logs`, `uninstall`, and `auth` that delegate to the local `twenty` CLI.
- **package.json**: Declares the app name, version, engines (Node 24+, Yarn 4), and adds `twenty-sdk` plus a `twenty` script that delegates to the local `twenty` CLI. Run `yarn twenty help` to list all available commands.
- **.gitignore**: Ignores common artifacts such as `node_modules`, `.yarn`, `generated/` (typed client), `dist/`, `build/`, coverage folders, log files, and `.env*` files.
- **yarn.lock**, **.yarnrc.yml**, **.yarn/**: Lock and configure the Yarn 4 toolchain used by the project.
- **.nvmrc**: Pins the Node.js version expected by the project.
- **eslint.config.mjs** and **tsconfig.json**: Provide linting and TypeScript configuration for your app's TypeScript sources.
- **README.md**: A short README in the app root with basic instructions.
- **src/app/**: The main place where you define your application-as-code:
- `application.config.ts`: Global configuration for your app (metadata and runtime wiring). See "Application config" below.
- `*.role.ts`: Role definitions used by your serverless functions. See "Default function role" below.
- `*.object.ts`: Custom object definitions.
- `*.function.ts`: Serverless function definitions.
- **src/utils/**: Optional folder for handler implementations and utilities.
- **public/**: A folder for storing public assets (images, fonts, static files) that will be served with your application. Files placed here are uploaded during sync and accessible at runtime.
- **src/**: The main place where you define your application-as-code
### Entity detection
The SDK detects entities by parsing your TypeScript files for **`export default define<Entity>({...})`** calls. Each entity type has a corresponding helper function exported from `twenty-sdk`:
| Helper function | Entity type |
|-----------------|-------------|
| `defineObject()` | Custom object definitions |
| `defineLogicFunction()` | Logic function definitions |
| `defineFrontComponent()` | Front component definitions |
| `defineRole()` | Role definitions |
| `defineField()` | Field extensions for existing objects |
| `defineView()` | Saved view definitions |
| `defineNavigationMenuItem()` | Navigation menu item definitions |
| `defineSkill()` | AI agent skill definitions |
<Note>
**File naming is flexible.** Entity detection is AST-based — the SDK scans your source files for the `export default define<Entity>({...})` pattern. You can organize your files and folders however you like. Grouping by entity type (e.g., `logic-functions/`, `roles/`) is just a convention for code organization, not a requirement.
</Note>
Example of a detected entity:
```typescript
// This file can be named anything and placed anywhere in src/
import { defineObject, FieldType } from 'twenty-sdk';
export default defineObject({
universalIdentifier: '...',
nameSingular: 'postCard',
// ... rest of config
});
```
Later commands will add more files and folders:
- `yarn app:generate` will create a `generated/` folder (typed Twenty client + workspace types).
- `yarn app:create-entity` will add entity definition files under `src/app/` for your custom objects, functions, or roles.
l
- `yarn twenty app:dev` will auto-generate a typed API client in `node_modules/twenty-sdk/generated` (typed Twenty client + workspace types).
- `yarn twenty entity:add` will add entity definition files under `src/` for your custom objects, functions, front components, roles, skills, and more.
## Authentication
The first time you run `yarn auth:login`, you'll be prompted for:
The first time you run `yarn twenty auth:login`, you'll be prompted for:
- API URL (defaults to http://localhost:3000 or your current workspace profile)
- API key
@@ -179,25 +186,25 @@ Your credentials are stored per-user in `~/.twenty/config.json`. You can maintai
Once you've switched workspaces with `auth:switch`, all subsequent commands will use that workspace by default. You can still override it temporarily with `--workspace <name>`.
Once you've switched workspaces with `yarn twenty auth:switch`, all subsequent commands will use that workspace by default. You can still override it temporarily with `--workspace <name>`.
## Use the SDK resources (types & config)
@@ -205,16 +212,21 @@ The twenty-sdk provides typed building blocks and helper functions you use insid
### Helper functions
The SDK provides four helper functions with built-in validation for defining your app entities:
The SDK provides helper functions for defining your app entities. As described in [Entity detection](#entity-detection), you must use `export default define<Entity>({...})` for your entities to be detected:
These functions validate your configuration at runtime and provide better IDE autocompletion and type safety.
These functions validate your configuration at build time and provide IDE autocompletion and type safety.
### Defining objects
@@ -295,80 +307,35 @@ Key points:
- The `universalIdentifier` must be unique and stable across deployments.
- Each field requires a `name`, `type`, `label`, and its own stable `universalIdentifier`.
- The `fields` array is optional — you can define objects without custom fields.
- You can scaffold new objects using `yarn app:create-entity`, which guides you through naming, fields, and relationships.
- You can scaffold new objects using `yarn twenty entity:add`, which guides you through naming, fields, and relationships.
<Note>
**Base fields are created automatically.** When you define a custom object, Twenty automatically adds standard fields such as `name`, `createdAt`, `updatedAt`, `createdBy`, `position`, and `deletedAt`. You don't need to define these in your `fields` array — only add your custom fields.
**Base fields are created automatically.** When you define a custom object, Twenty automatically adds standard fields
such as `id`, `name`, `createdAt`, `updatedAt`, `createdBy`, `updatedBy` and `deletedAt`.
You don't need to define these in your `fields` array — only add your custom fields.
You can override default fields by defining a field with the same name in your `fields` array,
- `universalIdentifier` fields are deterministic IDs you own; generate them once and keep them stable across syncs.
- `applicationVariables` become environment variables for your functions (for example, `DEFAULT_RECIPIENT_NAME` is available as `process.env.DEFAULT_RECIPIENT_NAME`).
- `functionRoleUniversalIdentifier` must match the role you define in your `*.role.ts` file (see below).
- `defaultRoleUniversalIdentifier` must match the role file (see below).
- `postInstallLogicFunctionUniversalIdentifier` (optional) points to a logic function that runs automatically after the app is installed. See [Post-install functions](#post-install-functions).
#### Roles and permissions
Applications can define roles that encapsulate permissions on your workspace's objects and actions. The field `functionRoleUniversalIdentifier` in `application.config.ts` designates the default role used by your app's serverless functions.
Applications can define roles that encapsulate permissions on your workspace's objects and actions. The field `defaultRoleUniversalIdentifier` in `application-config.ts` designates the default role used by your app's logic functions.
- The runtime API key injected as `TWENTY_API_KEY` is derived from this default function role.
- The typed client will be restricted to the permissions granted to that role.
@@ -403,14 +372,14 @@ Applications can define roles that encapsulate permissions on your workspace's o
When you scaffold a new app, the CLI also creates a default role file. Use `defineRole()` to define roles with built-in validation:
```typescript
// src/app/default-function.role.ts
// src/roles/default-role.ts
import { defineRole, PermissionFlag } from 'twenty-sdk';
The `universalIdentifier` of this role is then referenced in `application.config.ts` as `functionRoleUniversalIdentifier`. In other words:
The `universalIdentifier` of this role is then referenced in `application-config.ts` as `defaultRoleUniversalIdentifier`. In other words:
- **\*.role.ts** defines what the default function role can do.
- **application.config.ts** points to that role so your functions inherit its permissions.
- **application-config.ts** points to that role so your functions inherit its permissions.
Notes:
- Start from the scaffolded role, then progressively restrict it following least‑privilege.
@@ -453,22 +422,17 @@ Notes:
- `permissionFlags` control access to platform-level capabilities. Keep them minimal; add only what you need.
- See a working example in the Hello World app: [`packages/twenty-apps/hello-world/src/roles/function-role.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/hello-world/src/roles/function-role.ts).
### Serverless function config and entrypoint
### Logic function config and entrypoint
Each function file uses `defineFunction()` to export a configuration with a handler and optional triggers. Use the `*.function.ts` file suffix for automatic detection.
Each function file uses `defineLogicFunction()` to export a configuration with a handler and optional triggers.
```typescript
// src/app/createPostCard.function.ts
import { defineFunction } from 'twenty-sdk';
// src/app/createPostCard.logic-function.ts
import { defineLogicFunction } from 'twenty-sdk';
import type { DatabaseEventPayload, ObjectRecordCreateEvent, CronPayload, RoutePayload } from 'twenty-sdk';
import Twenty, { type Person } from '../../generated';
import Twenty, { type Person } from '~/generated';
- The `triggers` array is optional. Functions without triggers can be used as utility functions called by other functions.
- You can mix multiple trigger types in a single function.
### Post-install functions
A post-install function is a logic function that runs automatically after your app is installed on a workspace. This is useful for one-time setup tasks such as seeding default data, creating initial records, or configuring workspace settings.
When you scaffold a new app with `create-twenty-app`, a post-install function is generated for you at `src/logic-functions/post-install.ts`:
**To migrate existing functions:** Update your handler to destructure from `event.body`, `event.queryStringParameters`, or `event.pathParameters` instead of directly from the params object.
</Warning>
When a route trigger invokes your function, it receives a `RoutePayload` object that follows the AWS HTTP API v2 format. Import the type from `twenty-sdk`:
When a route trigger invokes your logic function, it receives a `RoutePayload` object that follows the AWS HTTP API v2 format. Import the type from `twenty-sdk`:
```typescript
import { defineFunction, type RoutePayload } from 'twenty-sdk';
import { defineLogicFunction, type RoutePayload } from 'twenty-sdk';
const handler = async (event: RoutePayload) => {
// Access request data
@@ -580,10 +592,10 @@ The `RoutePayload` type has the following structure:
### Forwarding HTTP headers
By default, HTTP headers from incoming requests are **not** passed to your serverless function for security reasons. To access specific headers, explicitly list them in the `forwardedRequestHeaders` array:
By default, HTTP headers from incoming requests are **not** passed to your logic function for security reasons. To access specific headers, explicitly list them in the `forwardedRequestHeaders` array:
- **Scaffolded**: Run `yarn app:create-entity` and choose the option to add a new function. This generates a starter file with a handler and config.
- **Manual**: Create a new `*.function.ts` file and use `defineFunction()`, following the same pattern.
- **Scaffolded**: Run `yarn twenty entity:add` and choose the option to add a new logic function. This generates a starter file with a handler and config.
- **Manual**: Create a new `*.logic-function.ts` file and use `defineLogicFunction()`, following the same pattern.
### Marking a logic function as a tool
Logic functions can be exposed as **tools** for AI agents and workflows. When a function is marked as a tool, it becomes discoverable by Twenty's AI features and can be selected as a step in workflow automations.
To mark a logic function as a tool, set `isTool: true` and provide a `toolInputSchema` describing the expected input parameters using [JSON Schema](https://json-schema.org/):
description: 'Enrich a company record with external data',
timeoutSeconds: 10,
handler,
isTool: true,
toolInputSchema: {
type: 'object',
properties: {
companyName: {
type: 'string',
description: 'The name of the company to enrich',
},
domain: {
type: 'string',
description: 'The company website domain (optional)',
},
},
required: ['companyName'],
},
});
```
Key points:
- **`isTool`** (`boolean`, default: `false`): When set to `true`, the function is registered as a tool and becomes available to AI agents and workflow automations.
- **`toolInputSchema`** (`object`, optional): A JSON Schema object that describes the parameters your function accepts. AI agents use this schema to understand what inputs the tool expects and to validate calls. If omitted, the schema defaults to `{ type: 'object', properties: {} }` (no parameters).
- Functions with `isTool: false` (or unset) are **not** exposed as tools. They can still be executed directly or called by other functions, but will not appear in tool discovery.
- **Tool naming**: When exposed as a tool, the function name is automatically normalized to `logic_function_<name>` (lowercased, non-alphanumeric characters replaced with underscores). For example, `enrich-company` becomes `logic_function_enrich_company`.
- You can combine `isTool` with triggers — a function can be both a tool (callable by AI agents) and triggered by events (cron, database events, routes) at the same time.
<Note>
**Write a good `description`.** AI agents rely on the function's `description` field to decide when to use the tool. Be specific about what the tool does and when it should be called.
</Note>
### Front components
Front components let you build custom React components that render within Twenty's UI. Use `defineFrontComponent()` to define components with built-in validation:
```typescript
// src/my-widget.front-component.tsx
import { defineFrontComponent } from 'twenty-sdk';
- Front components are React components that render in isolated contexts within Twenty.
- Use the `*.front-component.tsx` file suffix for automatic detection.
- The `component` field references your React component.
- Components are built and synced automatically during `yarn twenty app:dev`.
You can create new front components in two ways:
- **Scaffolded**: Run `yarn twenty entity:add` and choose the option to add a new front component.
- **Manual**: Create a new `*.front-component.tsx` file and use `defineFrontComponent()`.
### Skills
Skills define reusable instructions and capabilities that AI agents can use within your workspace. Use `defineSkill()` to define skills with built-in validation:
description: 'Guides the AI agent through a structured sales outreach process',
icon: 'IconBrain',
content: `You are a sales outreach assistant. When reaching out to a prospect:
1. Research the company and recent news
2. Identify the prospect's role and likely pain points
3. Draft a personalized message referencing specific details
4. Keep the tone professional but conversational`,
});
```
Key points:
- `name` is a unique identifier string for the skill (kebab-case recommended).
- `label` is the human-readable display name shown in the UI.
- `content` contains the skill instructions — this is the text the AI agent uses.
- `icon` (optional) sets the icon displayed in the UI.
- `description` (optional) provides additional context about the skill's purpose.
You can create new skills in two ways:
- **Scaffolded**: Run `yarn twenty entity:add` and choose the option to add a new skill.
- **Manual**: Create a new file and use `defineSkill()`, following the same pattern.
### Generated typed client
Run yarn app:generate to create a local typed client in generated/ based on your workspace schema. Use it in your functions:
The typed client is auto-generated by `yarn twenty app:dev` and stored in `node_modules/twenty-sdk/generated` based on your workspace schema. Use it in your functions:
The client is re-generated by `yarn app:generate`. Re-run after changing your objects and `yarn app:sync` or when onboarding to a new workspace.
The client is re-generated automatically by `yarn twenty app:dev` whenever your objects or fields change.
#### Runtime credentials in serverless functions
#### Runtime credentials in logic functions
When your function runs on Twenty, the platform injects credentials as environment variables before your code executes:
@@ -643,46 +790,84 @@ When your function runs on Twenty, the platform injects credentials as environme
Notes:
- You do not need to pass URL or API key to the generated client. It reads `TWENTY_API_URL` and `TWENTY_API_KEY` from process.env at runtime.
- The API key's permissions are determined by the role referenced in your `application.config.ts` via `functionRoleUniversalIdentifier`. This is the default role used by serverless functions of your application.
- Applications can define roles to follow least‑privilege. Grant only the permissions your functions need, then point `functionRoleUniversalIdentifier` to that role's universal identifier.
- The API key's permissions are determined by the role referenced in your `application-config.ts` via `defaultRoleUniversalIdentifier`. This is the default role used by logic functions of your application.
- Applications can define roles to follow least‑privilege. Grant only the permissions your functions need, then point `defaultRoleUniversalIdentifier` to that role's universal identifier.
#### Uploading files
The generated `Twenty` client includes an `uploadFile` method for attaching files to file-type fields on your workspace objects. Because standard GraphQL clients do not support multipart file uploads natively, the client provides this dedicated method that implements the [GraphQL multipart request specification](https://github.com/jaydenseric/graphql-multipart-request-spec) under the hood.
| `fileBuffer` | `Buffer` | The raw file contents |
| `filename` | `string` | The name of the file (used for storage and display) |
| `contentType` | `string` | MIME type of the file (defaults to `application/octet-stream` if omitted) |
| `fieldMetadataUniversalIdentifier` | `string` | The `universalIdentifier` of the file-type field on your object |
Key points:
- The method sends the file to the **metadata endpoint** (not the main GraphQL endpoint), where the upload mutation is resolved.
- It uses the field's `universalIdentifier` (not its workspace-specific ID), so your upload code works across any workspace where your app is installed — consistent with how apps reference fields everywhere else.
- The returned `url` is a signed URL you can use to access the uploaded file.
### Hello World example
Explore a minimal, end-to-end example that demonstrates objects, functions, and multiple triggers [here](https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/hello-world):
Explore a minimal, end-to-end example that demonstrates objects, logic functions, front components, and multiple triggers [here](https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/hello-world):
## Manual setup (without the scaffolder)
While we recommend using `create-twenty-app` for the best getting-started experience, you can also set up a project manually. Do not install the CLI globally. Instead, add `twenty-sdk` as a local dependency and wire scripts in your package.json:
While we recommend using `create-twenty-app` for the best getting-started experience, you can also set up a project manually. Do not install the CLI globally. Instead, add `twenty-sdk` as a local dependency and wire a single script in your package.json:
```bash filename="Terminal"
yarn add -D twenty-sdk
```
Then add scripts like these:
Then add a `twenty` script:
```json filename="package.json"
{
"scripts": {
"auth": "twenty auth login",
"generate": "twenty app generate",
"dev": "twenty app dev",
"sync": "twenty app sync",
"uninstall": "twenty app uninstall",
"logs": "twenty app logs",
"create-entity": "twenty app add",
"help": "twenty --help"
"twenty": "twenty"
}
}
```
Now you can run the same commands via Yarn, e.g. `yarn app:dev`, `yarn app:sync`, etc.
Now you can run all commands via `yarn twenty <command>`, e.g. `yarn twenty app:dev`, `yarn twenty help`, etc.
## Troubleshooting
- Authentication errors: run `yarn auth:login` and ensure your API key has the required permissions.
- Authentication errors: run `yarn twenty auth:login` and ensure your API key has the required permissions.
- Cannot connect to server: verify the API URL and that the Twenty server is reachable.
- Types or client missing/outdated: run `yarn app:generate` and then `yarn app:dev`.
- Dev mode not syncing: ensure `yarn app:dev` is running and that changes are not ignored by your environment.
- Types or client missing/outdated: restart `yarn twenty app:dev` — it auto-generates the typed client.
- Dev mode not syncing: ensure `yarn twenty app:dev` is running and that changes are not ignored by your environment.
Discord Help Channel: https://discord.com/channels/1130383047699738754/1130386664812982322
**Environment-only mode:** If you set `IS_CONFIG_VARIABLES_IN_DB_ENABLED=false`, add these variables to your `.env` file instead.
</Warning>
## Serverless Functions
## Logic Functions
Twenty supports serverless functions for workflows and custom logic. The execution environment is configured via the `SERVERLESS_TYPE` environment variable.
Twenty supports logic functions for workflows and custom logic. The execution environment is configured via the `SERVERLESS_TYPE` environment variable.
<Warning>
**Security Notice:** The local serverless driver (`SERVERLESS_TYPE=LOCAL`) runs code directly on the host in a Node.js process with no sandboxing. It should only be used for trusted code in development. For production deployments handling untrusted code, we highly recommend using `SERVERLESS_TYPE=LAMBDA` or `SERVERLESS_TYPE=DISABLED`.
**Security Notice:** The local driver (`SERVERLESS_TYPE=LOCAL`) runs code directly on the host in a Node.js process with no sandboxing. It should only be used for trusted code in development. For production deployments handling untrusted code, we highly recommend using `SERVERLESS_TYPE=LAMBDA` or `SERVERLESS_TYPE=DISABLED`.
</Warning>
### Available Drivers
| Driver | Environment Variable | Use Case | Security Level |
When using `SERVERLESS_TYPE=DISABLED`, any attempt to execute a serverless function will return an error. This is useful if you want to run Twenty without serverless function capabilities.
When using `SERVERLESS_TYPE=DISABLED`, any attempt to execute a logic function will return an error. This is useful if you want to run Twenty without logic function capabilities.
@@ -9,18 +9,15 @@ description: أنشئ وأدِر تخصيصات Twenty على هيئة كود.
## ما هي التطبيقات؟
تتيح لك التطبيقات إنشاء وإدارة تخصيصات Twenty **ككود**. بدلًا من تكوين كل شيء عبر واجهة المستخدم، تُعرِّف نموذج بياناتك ووظائف بلا خادم في الكود — مما يجعل الإنشاء والصيانة والنشر إلى مساحات عمل متعددة أسرع.
تتيح لك التطبيقات إنشاء وإدارة تخصيصات Twenty **ككود**. بدلًا من تكوين كل شيء عبر واجهة المستخدم، تُعرِّف نموذج بياناتك ووظائف المنطق في الكود — مما يجعل البناء والصيانة والنشر إلى مساحات عمل متعددة أسرع.
**ما الذي يمكنك فعله اليوم:**
* عرِّف كائنات وحقولًا مخصصة على شكل كود (نموذج بيانات مُدار)
* أنشئ وظائف بلا خادم مع مشغلات مخصصة
* أنشئ وظائف منطقية مع مشغلات مخصصة
* حدد المهارات لوكلاء الذكاء الاصطناعي
* انشر التطبيق نفسه عبر مساحات عمل متعددة
**قريبًا:**
* تخطيطات ومكونات واجهة مستخدم مخصصة
## المتطلبات الأساسية
* Node.js 24+ وYarn 4
@@ -31,40 +28,54 @@ description: أنشئ وأدِر تخصيصات Twenty على هيئة كود.
أنشئ تطبيقًا جديدًا باستخدام المُهيئ الرسمي، ثم قم بالمصادقة وابدأ التطوير:
```bash filename="Terminal"
# Scaffold a new app
# إنشاء تطبيق جديد (يتضمن جميع الأمثلة افتراضيًا)
npx create-twenty-app@latest my-twenty-app
cd my-twenty-app
# Authenticate using your API key (you'll be prompted)
yarn auth:login
# إذا كنت لا تستخدم yarn@4
corepack enable
yarn install
# Start dev mode: automatically syncs local changes to your workspace
yarn app:dev
# قم بالمصادقة باستخدام مفتاح واجهة برمجة التطبيقات الخاص بك (سيُطلب منك ذلك)
yarn twenty auth:login
# ابدأ وضع التطوير: يُزامن التغييرات المحلية تلقائيًا مع مساحة العمل الخاصة بك
yarn twenty app:dev
```
يدعم المُنشئ ثلاثة أوضاع للتحكم في ملفات الأمثلة التي سيتم تضمينها:
```bash filename="Terminal"
# الافتراضي (شامل): جميع الأمثلة (كائن، حقل، دالة منطقية، مكوّن الواجهة الأمامية، عرض، عنصر قائمة التنقل، مهارة)
npx create-twenty-app@latest my-app
# الأدنى: الملفات الأساسية فقط (application-config.ts و default-role.ts)
npx create-twenty-app@latest my-app --minimal
# التفاعلي: اختر الأمثلة التي تريد تضمينها
npx create-twenty-app@latest my-app --interactive
```
من هنا يمكنك:
```bash filename="Terminal"
# Add a new entity to your application (guided)
yarn app:create-entity
yarn twenty entity:add
# Generate a typed Twenty client and workspace entity types
├── application-config.ts # مطلوب - إعدادات التطبيق الرئيسية
├── roles/
│└── default-role.ts # الدور الافتراضي للدوال المنطقية
├── objects/
│ └── example-object.ts # تعريف كائن مخصص — مثال
├── fields/
│ └── example-field.ts # تعريف حقل مستقل — مثال
├── logic-functions/
│ ├── hello-world.ts # دالة منطقية — مثال
│ └── post-install.ts # دالة منطقية لما بعد التثبيت
├── front-components/
│ └── hello-world.tsx # مكوّن واجهة أمامية — مثال
├── views/
│ └── example-view.ts # تعريف عرض محفوظ — مثال
├── navigation-menu-items/
│ └── example-navigation-menu-item.ts # رابط تنقّل في الشريط الجانبي — مثال
└── skills/
└── example-skill.ts # تعريف مهارة لوكيل الذكاء الاصطناعي — مثال
```
### الاتفاقية فوق التهيئة
تستخدم التطبيقات نهج **الاتفاقية فوق التهيئة** حيث تُكتشف الكيانات عبر لاحقة اسم الملف. يتيح ذلك تنظيمًا مرنًا داخل مجلد `src/app/`:
| لاحقة الملف | نوع الكيان |
| --------------- | ---------------------- |
| `*.object.ts` | تعريفات كائنات مخصصة |
| `*.function.ts` | تعريفات وظائف بلا خادم |
| `*.role.ts` | تعريفات الأدوار |
### طرق تنظيم المجلدات المدعومة
يمكنك تنظيم الكيانات بأي من الأنماط التالية:
**تقليدي (حسب النوع):**
```text
src/app/
├── application.config.ts
├── objects/
│ └── postCard.object.ts
├── functions/
│ └── createPostCard.function.ts
└── roles/
└── admin.role.ts
```
**حسب الميزة:**
```text
src/app/
├── application.config.ts
└── post-card/
├── postCard.object.ts
├── createPostCard.function.ts
└── postCardAdmin.role.ts
```
**مسطح:**
```text
src/app/
├── application.config.ts
├── postCard.object.ts
├── createPostCard.function.ts
└── admin.role.ts
```
مع `--minimal`، سيتم إنشاء الملفات الأساسية فقط (`application-config.ts` و`roles/default-role.ts` و`logic-functions/post-install.ts`). مع `--interactive`، تختار ملفات الأمثلة التي تريد تضمينها.
بشكل عام:
* **package.json**: يصرّح باسم التطبيق والإصدار والمحرّكات (Node 24+، Yarn 4)، ويضيف `twenty-sdk` فضلًا عن نصوص مثل `dev` و`sync` و`generate` و`create-entity` و`logs` و`uninstall` و`auth` التي تفوِّض إلى `twenty` CLI المحلي.
* **package.json**: يصرّح باسم التطبيق والإصدار والمحرّكات (Node 24+، Yarn 4)، ويضيف `twenty-sdk` بالإضافة إلى نص برمجي `twenty` يفوِّض إلى `twenty` CLI المحلي. شغِّل `yarn twenty help` لعرض جميع الأوامر المتاحة.
* **.gitignore**: يتجاهل العناصر الشائعة مثل `node_modules` و`.yarn` و`generated/` (عميل مضبوط الأنواع) و`dist/` و`build/` ومجلدات التغطية وملفات السجلات وملفات `.env*`.
* **yarn.lock**، **.yarnrc.yml**، **.yarn/**: تقوم بقفل وتكوين حزمة أدوات Yarn 4 المستخدمة في المشروع.
* **.nvmrc**: يثبّت إصدار Node.js المتوقع للمشروع.
* **eslint.config.mjs** و**tsconfig.json**: يقدّمان إعدادات الفحص والتهيئة لـ TypeScript لمصادر TypeScript في تطبيقك.
* **public/**: مجلد لتخزين الأصول العامة (صور، خطوط، ملفات ثابتة) التي سيتم تقديمها مع تطبيقك. الملفات الموضوعة هنا تُرفع أثناء المزامنة وتكون متاحة أثناء وقت التشغيل.
* **src/**: المكان الرئيسي حيث تعرّف تطبيقك ككود
### اكتشاف الكيانات
يكتشف SDK الكيانات عبر تحليل ملفات TypeScript الخاصة بك بحثًا عن استدعاءات **`export default define<Entity>({...})`**. يحتوي كل نوع كيان على دالة مساعدة مقابلة يتم تصديرها من `twenty-sdk`:
**تسمية الملفات مرنة.** يعتمد اكتشاف الكيانات على بنية الشجرة المجردة (AST) — إذ يقوم SDK بفحص ملفات المصدر لديك بحثًا عن النمط `export default define<Entity>({...})`. يمكنك تنظيم ملفاتك ومجلداتك كيفما تشاء. التجميع حسب نوع الكيان (مثلًا، `logic-functions/` و`roles/`) هو مجرد عرف لتنظيم الشيفرة، وليس مطلبًا إلزاميًا.
</Note>
مثال على كيان تم اكتشافه:
```typescript
// This file can be named anything and placed anywhere in src/
import { defineObject, FieldType } from 'twenty-sdk';
export default defineObject({
universalIdentifier: '...',
nameSingular: 'postCard',
// ... rest of config
});
```
ستضيف الأوامر اللاحقة مزيدًا من الملفات والمجلدات:
* `yarn app:generate` will create a `generated/` folder (typed Twenty client + workspace types).
* `yarn app:create-entity` will add entity definition files under `src/app/` for your custom objects, functions, or roles.
l
* `yarn twenty app:dev` سيولّد تلقائيًا عميل API مضبوط الأنواع في `node_modules/twenty-sdk/generated` (عميل Twenty مضبوط الأنواع + أنواع مساحة العمل).
* `yarn twenty entity:add` سيضيف ملفات تعريف الكيانات ضمن `src/` لكائناتك المخصّصة، والوظائف، ومكوّنات الواجهة الأمامية، والأدوار، والمهارات، وغير ذلك.
## المصادقة
The first time you run `yarn auth:login`, you'll be prompted for:
في المرة الأولى التي تشغّل فيها `yarn twenty auth:login`، سيُطلب منك إدخال:
* عنوان URL لواجهة برمجة التطبيقات (الافتراضي http://localhost:3000 أو ملف تعريف مساحة العمل الحالية لديك)
* مفتاح واجهة برمجة التطبيقات
تُخزَّن بيانات اعتمادك لكل مستخدم في `~/.twenty/config.json`. يمكنك الاحتفاظ بملفات تعريف متعددة والتبديل باستخدام `--workspace <name>`.
تُخزَّن بيانات اعتمادك لكل مستخدم في `~/.twenty/config.json`. You can maintain multiple profiles and switch between them.
بمجرد أن تقوم بالتبديل بين مساحات العمل باستخدام `yarn twenty auth:switch`، ستستخدم جميع الأوامر اللاحقة تلك المساحة افتراضيًا. You can still override it temporarily with `--workspace <name>`.
## استخدم موارد SDK (الأنواع والتكوين)
يوفّر twenty-sdk كتلَ بناءٍ مضبوطة الأنواع ودوال مساعدة تستخدمها داخل تطبيقك. فيما يلي الأجزاء الأساسية التي ستتعامل معها غالبًا.
### دوال مساعدة
يوفّر SDK أربع دوال مساعدة مع تحقق مدمج لتعريف كيانات تطبيقك:
يوفّر SDK دوالًا مساعدة لتعريف كيانات تطبيقك. كما هو موضح في [اكتشاف الكيانات](#entity-detection)، يجب استخدام `export default define<Entity>({...})` كي يتم اكتشاف كياناتك:
* `universalIdentifier` يجب أن يكون فريدًا وثابتًا عبر عمليات النشر.
* يتطلب كل حقل `name` و`type` و`label` ومعرّف `universalIdentifier` ثابتًا خاصًا به.
* المصفوفة `fields` اختيارية — يمكنك تعريف كائنات بدون حقول مخصصة.
* You can scaffold new objects using `yarn app:create-entity`, which guides you through naming, fields, and relationships.
* يمكنك إنشاء كائنات جديدة باستخدام `yarn twenty entity:add`، والذي يرشدك خلال التسمية والحقول والعلاقات.
<Note>
**يتم إنشاء الحقول الأساسية تلقائيًا.** عند تعريف كائن مخصص، يضيف Twenty تلقائيًا حقولًا قياسية مثل `name` و`createdAt` و`updatedAt` و`createdBy` و`position` و`deletedAt`. لا تحتاج إلى تعريف هذه في مصفوفة `fields` — أضف فقط حقولك المخصصة.
**يتم إنشاء الحقول الأساسية تلقائيًا.** عند تعريف كائن مخصص، يضيف Twenty تلقائيًا حقولًا قياسية
مثل `id` و`name` و`createdAt` و`updatedAt` و`createdBy` و`updatedBy` و`deletedAt`.
لا تحتاج إلى تعريف هذه في مصفوفة `fields` — أضف فقط حقولك المخصصة.
يمكنك تجاوز الحقول الافتراضية من خلال تعريف حقل بالاسم نفسه في مصفوفة `fields` الخاصة بك،
لكن هذا غير مستحسن.
</Note>
<Accordion title="بديل: صياغة قائمة على المزيّنات">
يمكنك أيضًا تعريف كائنات باستخدام مزيّنات TypeScript. يستخدم هذا النهج صياغة معتمدة على الأصناف مع مزيّنات `@Object` و`@Field` و`@Relation`:
* حقول `universalIdentifier` هي معرّفات حتمية تخصك؛ أنشئها مرة واحدة واحتفظ بها ثابتة عبر عمليات المزامنة.
* `applicationVariables` تصبح متغيرات بيئة لوظائفك (على سبيل المثال، `DEFAULT_RECIPIENT_NAME` متاح كـ `process.env.DEFAULT_RECIPIENT_NAME`).
* `functionRoleUniversalIdentifier` يجب أن يطابق الدور الذي تعرّفه في ملف `*.role.ts` (انظر أدناه).
* `defaultRoleUniversalIdentifier` يجب أن يطابق ملف الدور (انظر أدناه).
* `postInstallLogicFunctionUniversalIdentifier` (optional) points to a logic function that runs automatically after the app is installed. See [Post-install functions](#post-install-functions).
#### الأدوار والصلاحيات
يمكن للتطبيقات تعريف أدوار تُغلّف الصلاحيات على كائنات وإجراءات مساحة العمل لديك. يعين الحقل `functionRoleUniversalIdentifier` في `application.config.ts` الدور الافتراضي الذي تستخدمه الوظائف بلا خادم في تطبيقك.
يمكن للتطبيقات تعريف أدوار تُغلّف الصلاحيات على كائنات وإجراءات مساحة العمل لديك. يعين الحقل `defaultRoleUniversalIdentifier` في `application-config.ts` الدور الافتراضي الذي تستخدمه وظائف المنطق في تطبيقك.
* مفتاح واجهة البرمجة في وقت التشغيل المحقون باسم `TWENTY_API_KEY` مستمد من دور الوظيفة الافتراضي هذا.
* سيُقيَّد العميل مضبوط الأنواع بالأذونات الممنوحة لذلك الدور.
@@ -394,14 +374,14 @@ export default defineApp({
عند توليد تطبيق جديد بالقالب، ينشئ CLI أيضًا ملف دور افتراضي. استخدم `defineRole()` لتعريف أدوار مع تحقق مدمج:
```typescript
// src/app/default-function.role.ts
// src/roles/default-role.ts
import { defineRole, PermissionFlag } from 'twenty-sdk';
يُشار بعد ذلك إلى `universalIdentifier` لهذا الدور في `application.config.ts` باسم `functionRoleUniversalIdentifier`. بعبارة أخرى:
يُشار بعد ذلك إلى `universalIdentifier` لهذا الدور في `application-config.ts` باسم `defaultRoleUniversalIdentifier`. بعبارة أخرى:
* **\\*.role.ts** يحدد ما يمكن أن يفعله الدور الافتراضي للوظيفة.
* **application.config.ts** يشير إلى ذلك الدور بحيث ترث وظائفك صلاحياته.
* **application-config.ts** يشير إلى ذلك الدور بحيث ترث وظائفك أذوناته.
الملاحظات:
@@ -445,22 +425,17 @@ export default defineRole({
* `permissionFlags` تتحكم في الوصول إلى القدرات على مستوى المنصة. اجعلها في الحد الأدنى؛ أضف فقط ما تحتاجه.
* اطّلع على مثال عملي في تطبيق Hello World: [`packages/twenty-apps/hello-world/src/roles/function-role.ts`](https://github.com/twentyhq/twenty/blob/main/packages/twenty-apps/hello-world/src/roles/function-role.ts).
### تكوين وظيفة بلا خادم ونقطة الدخول
### تكوين الوظيفة المنطقية ونقطة الدخول
كل ملف وظيفة يستخدم `defineFunction()` لتصدير تكوين مع معالج ومشغلات اختيارية. استخدم لاحقة الملف `*.function.ts` للاكتشاف التلقائي.
كل ملف وظيفة يستخدم `defineLogicFunction()` لتصدير تكوين مع معالج ومشغّلات اختيارية.
```typescript
// src/app/createPostCard.function.ts
import { defineFunction } from 'twenty-sdk';
// src/app/createPostCard.logic-function.ts
import { defineLogicFunction } from 'twenty-sdk';
import type { DatabaseEventPayload, ObjectRecordCreateEvent, CronPayload, RoutePayload } from 'twenty-sdk';
import Twenty, { type Person } from '../../generated';
import Twenty, { type Person } from '~/generated';
> مثال: `path: '/post-card/create',` -> الاستدعاء على `<APP_URL>/s/post-card/create`
* **cron**: يشغّل وظيفتك على جدول باستخدام تعبير CRON.
* **databaseEvent**: Runs on workspace object lifecycle events. When the event operation is `updated`, specific fields to listen to can be specified in the `updatedFields` array. If left undefined or empty, any update will trigger the function.
* **databaseEvent**: يعمل على أحداث دورة حياة كائنات مساحة العمل. عندما تكون عملية الحدث هي `updated`، يمكن تحديد الحقول المحددة المراد الاستماع إليها في مصفوفة `updatedFields`. إذا تُركت غير معرّفة أو فارغة، فسيؤدي أي تحديث إلى تشغيل الدالة.
> e.g. `person.updated`
> مثال: `person.updated`
الملاحظات:
* المصفوفة `triggers` اختيارية. يمكن استخدام الوظائف بدون مشغلات كوظائف مساعدة تُستدعى بواسطة وظائف أخرى.
* يمكنك مزج أنواع متعددة من المشغلات في وظيفة واحدة.
### Route trigger payload
### Post-install functions
A post-install function is a logic function that runs automatically after your app is installed on a workspace. This is useful for one-time setup tasks such as seeding default data, creating initial records, or configuring workspace settings.
عند إنشاء هيكل تطبيق جديد باستخدام `create-twenty-app`، يتم إنشاء دالة ما بعد التثبيت لك في `src/logic-functions/post-install.ts`:
يمكنك أيضًا تنفيذ دالة ما بعد التثبيت يدويًا في أي وقت باستخدام CLI:
```bash filename="Terminal"
yarn twenty function:execute --postInstall
```
النقاط الرئيسية:
* دوال ما بعد التثبيت هي دوال منطقية قياسية — فهي تستخدم `defineLogicFunction()` مثل أي دالة أخرى.
* حقل `postInstallLogicFunctionUniversalIdentifier` في `defineApplication()` اختياري. إذا تم تجاهله، لن يتم تشغيل أي دالة بعد التثبيت.
* تم تعيين مهلة افتراضية إلى 300 ثانية (5 دقائق) للسماح بمهام الإعداد الأطول مثل تهيئة البيانات.
* لا تحتاج دوال ما بعد التثبيت إلى مُشغِّلات — حيث يستدعيها النظام الأساسي أثناء التثبيت أو يدويًا عبر `function:execute --postInstall`.
### حمولة مشغل المسار
<Warning>
**Breaking change (v1.16, January 2026):** The route trigger payload format has changed. Prior to v1.16, query parameters, path parameters, and body were sent directly as the payload. Starting with v1.16, they are nested inside a structured `RoutePayload` object.
**تغيير غير متوافق (v1.16، يناير 2026):** لقد تغير تنسيق حمولة مشغل المسار. قبل v1.16، كانت معلمات الاستعلام، ومعلمات المسار، وجسم الطلب تُرسل مباشرةً كحمولة. بدءًا من v1.16، أصبحت متداخلة داخل كائن منظَّم `RoutePayload`.
**To migrate existing functions:** Update your handler to destructure from `event.body`, `event.queryStringParameters`, or `event.pathParameters` instead of directly from the params object.
**لترحيل الدوال الحالية:** حدّث المعالج لديك لفكّ البنية من `event.body` أو `event.queryStringParameters` أو `event.pathParameters` بدلاً من القراءة مباشرةً من كائن params.
</Warning>
When a route trigger invokes your function, it receives a `RoutePayload` object that follows the AWS HTTP API v2 format. Import the type from `twenty-sdk`:
عندما يستدعي مشغّل المسار وظيفتك المنطقية، يتلقى كائنًا من النوع `RoutePayload` يتبع تنسيق AWS HTTP API v2. استورد النوع من `twenty-sdk`:
```typescript
import { defineFunction, type RoutePayload } from 'twenty-sdk';
import { defineLogicFunction, type RoutePayload } from 'twenty-sdk';
By default, HTTP headers from incoming requests are **not** passed to your serverless function for security reasons. To access specific headers, explicitly list them in the `forwardedRequestHeaders` array:
افتراضيًا، **لا** تُمرَّر رؤوس HTTP من الطلبات الواردة إلى وظيفتك المنطقية لأسباب أمنية. للوصول إلى رؤوس محددة، قم بإدراجها صراحةً في مصفوفة `forwardedRequestHeaders`:
Header names are normalized to lowercase. Access them using lowercase keys (for example, `event.headers['content-type']`).
تُحوَّل أسماء الرؤوس إلى أحرف صغيرة. يمكنك الوصول إليها باستخدام مفاتيح بأحرف صغيرة (على سبيل المثال، `event.headers['content-type']`).
</Note>
يمكنك إنشاء وظائف جديدة بطريقتين:
* **Scaffolded**: Run `yarn app:create-entity` and choose the option to add a new function. يُولّد هذا ملفًا مبدئيًا مع معالج وتكوين.
* **يدوي**: أنشئ ملفًا جديدًا `*.function.ts` واستخدم `defineFunction()` مع اتباع النمط نفسه.
* **مُنشأ بالقالب**: شغّل `yarn twenty entity:add` واختر خيار إضافة وظيفة منطقية جديدة. يُولّد هذا ملفًا مبدئيًا مع معالج وتكوين.
* **يدوي**: أنشئ ملفًا جديدًا `*.logic-function.ts` واستخدم `defineLogicFunction()` مع اتباع النمط نفسه.
### تمييز دالة منطقية كأداة
يمكن إتاحة الدوال المنطقية بوصفها **أدوات** لوكلاء الذكاء الاصطناعي وسير العمل. عندما يتم تمييز دالة كأداة، تصبح قابلة للاكتشاف بواسطة ميزات الذكاء الاصطناعي الخاصة بـ Twenty ويمكن اختيارها كخطوة في أتمتة سير العمل.
description: 'Enrich a company record with external data',
timeoutSeconds: 10,
handler,
isTool: true,
toolInputSchema: {
type: 'object',
properties: {
companyName: {
type: 'string',
description: 'The name of the company to enrich',
},
domain: {
type: 'string',
description: 'The company website domain (optional)',
},
},
required: ['companyName'],
},
});
```
النقاط الرئيسية:
* **`isTool`** (`boolean`, الافتراضي: `false`): عند ضبطه على `true`، يتم تسجيل الدالة كأداة وتصبح متاحة لوكلاء الذكاء الاصطناعي ولأتمتة سير العمل.
* **`toolInputSchema`** (`object`, اختياري): كائن JSON Schema يصف المعلمات التي تقبلها دالتك. يستخدم وكلاء الذكاء الاصطناعي هذا المخطط لفهم المدخلات التي تتوقعها الأداة وللتحقق من صحة الاستدعاءات. إذا تم إغفاله، فالقيمة الافتراضية للمخطط هي `{ type: 'object', properties: {} }` (من دون معلمات).
* الدوال التي لديها `isTool: false` (أو غير معيَّنة) **غير** معروضة كأدوات. لا يزال بالإمكان تنفيذها مباشرةً أو استدعاؤها بواسطة دوال أخرى، لكنها لن تظهر في اكتشاف الأدوات.
* **تسمية الأداة**: عند كشفها كأداة، يتم تطبيع اسم الدالة تلقائيًا إلى `logic_function_<name>` (تحويله إلى أحرف صغيرة، واستبدال المحارف غير الأبجدية الرقمية بشرطات سفلية). على سبيل المثال، `enrich-company` تصبح `logic_function_enrich_company`.
* يمكنك دمج `isTool` مع المشغِّلات — إذ يمكن للدالة أن تكون أداة (قابلة للاستدعاء من قِبل وكلاء الذكاء الاصطناعي) وأن تُشغَّل بواسطة أحداث (cron، وأحداث قاعدة البيانات، والمسارات) في الوقت نفسه.
<Note>
**اكتب `description` جيدًا.** يعتمد وكلاء الذكاء الاصطناعي على حقل `description` الخاص بالدالة لتحديد وقت استخدام الأداة. كن محددًا بشأن ما تفعله الأداة ومتى ينبغي استدعاؤها.
</Note>
### المكوّنات الأمامية
تتيح لك المكوّنات الأمامية إنشاء مكوّنات React مخصّصة تُعرَض داخل واجهة مستخدم Twenty. استخدم `defineFrontComponent()` لتعريف مكوّنات مع تحقّق مدمج:
```typescript
// src/my-widget.front-component.tsx
import { defineFrontComponent } from 'twenty-sdk';
تُحدِّد المهارات تعليمات وإمكانات قابلة لإعادة الاستخدام يمكن لوكلاء الذكاء الاصطناعي استخدامها داخل مساحة العمل لديك. استخدم `defineSkill()` لتعريف مهارات مع تحقّق مدمج:
* **يدوي**: أنشئ ملفًا جديدًا واستخدم `defineSkill()` مع اتباع النمط نفسه.
### عميل مُولَّد مضبوط الأنواع
Run yarn app:generate to create a local typed client in generated/ based on your workspace schema. استخدمه في وظائفك:
يُولَّد العميل مضبوط الأنواع تلقائيًا بواسطة `yarn twenty app:dev` ويُخزَّن في `node_modules/twenty-sdk/generated` استنادًا إلى مخطط مساحة العمل لديك. استخدمه في وظائفك:
The client is re-generated by `yarn app:generate`. Re-run after changing your objects and `yarn app:sync` or when onboarding to a new workspace.
يُعاد توليد العميل تلقائيًا بواسطة `yarn twenty app:dev` كلما تغيّرت كائناتك أو حقولك.
#### بيانات الاعتماد في وقت التشغيل في الوظائف بلا خادم
#### بيانات الاعتماد وقت التشغيل في الوظائف المنطقية
عندما تعمل وظيفتك على Twenty، يقوم النظام الأساسي بحقن بيانات الاعتماد كمتغيرات بيئة قبل تنفيذ كودك:
@@ -643,45 +804,38 @@ The client is re-generated by `yarn app:generate`. Re-run after changing your ob
الملاحظات:
* لا تحتاج إلى تمرير عنوان URL أو مفتاح واجهة برمجة التطبيقات إلى العميل المُولَّد. يقوم بقراءة `TWENTY_API_URL` و`TWENTY_API_KEY` من process.env وقت التشغيل.
* تُحدَّد أذونات مفتاح واجهة برمجة التطبيقات بواسطة الدور المشار إليه في `application.config.ts` عبر `functionRoleUniversalIdentifier`. هذا هو الدور الافتراضي الذي تستخدمه الوظائف بلا خادم في تطبيقك.
* يمكن للتطبيقات تعريف أدوار لاتباع مبدأ أقل الامتياز. امنح فقط الأذونات التي تحتاجها وظائفك، ثم وجّه `functionRoleUniversalIdentifier` إلى المعرّف الشامل لذلك الدور.
* تُحدَّد أذونات مفتاح واجهة برمجة التطبيقات بواسطة الدور المشار إليه في `application-config.ts` عبر `defaultRoleUniversalIdentifier`. هذا هو الدور الافتراضي الذي تستخدمه الوظائف المنطقية في تطبيقك.
* يمكن للتطبيقات تعريف أدوار لاتباع مبدأ أقل الامتياز. امنح فقط الأذونات التي تحتاجها وظائفك، ثم وجّه `defaultRoleUniversalIdentifier` إلى المعرّف الشامل لذلك الدور.
### مثال Hello World
استكشف مثالًا بسيطًا شاملًا من البداية إلى النهاية يوضح الكائنات والوظائف ومشغلات متعددة [هنا](https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/hello-world):
استكشف مثالًا بسيطًا شاملًا من البداية إلى النهاية يوضح الكائنات والوظائف المنطقية والمكوّنات الأمامية ومشغّلات متعددة [هنا](https://github.com/twentyhq/twenty/tree/main/packages/twenty-apps/hello-world):
## إعداد يدوي (بدون المهيئ)
بينما نوصي باستخدام `create-twenty-app` للحصول على أفضل تجربة للبدء، يمكنك أيضًا إعداد مشروع يدويًا. لا تثبّت CLI عالميًا. بدل ذلك، أضف `twenty-sdk` كاعتماد محلي ووصل السكربتات في ملف package.json لديك:
بينما نوصي باستخدام `create-twenty-app` للحصول على أفضل تجربة للبدء، يمكنك أيضًا إعداد مشروع يدويًا. لا تثبّت CLI عالميًا. بدل ذلك، أضف `twenty-sdk` كاعتماد محلي واربط سكربتًا واحدًا في ملف package.json لديك:
```bash filename="Terminal"
yarn add -D twenty-sdk
```
ثم أضف نصوصًا مثل هذه:
ثم أضف سكربتًا باسم `twenty`:
```json filename="package.json"
{
"scripts": {
"auth": "twenty auth login",
"generate": "twenty app generate",
"dev": "twenty app dev",
"sync": "twenty app sync",
"uninstall": "twenty app uninstall",
"logs": "twenty app logs",
"create-entity": "twenty app add",
"help": "twenty --help"
"twenty": "twenty"
}
}
```
Now you can run the same commands via Yarn, e.g. `yarn app:dev`, `yarn app:sync`, etc.
الآن يمكنك تشغيل جميع الأوامر عبر `yarn twenty <command>`، مثلًا: `yarn twenty app:dev`، `yarn twenty help`، إلخ.
## استكشاف الأخطاء وإصلاحها
* Authentication errors: run `yarn auth:login` and ensure your API key has the required permissions.
* أخطاء المصادقة: شغّل `yarn twenty auth:login` وتأكد من أن مفتاح واجهة برمجة التطبيقات لديك يمتلك الأذونات المطلوبة.
* يتعذّر الاتصال بالخادم: تحقق من عنوان URL لواجهة البرمجة وأن خادم Twenty قابل للوصول.
* Types or client missing/outdated: run `yarn app:generate` and then `yarn app:dev`.
* Dev mode not syncing: ensure `yarn app:dev` is running and that changes are not ignored by your environment.
* الأنواع أو العميل مفقود/قديم: أعد تشغيل `yarn twenty app:dev` — فهو ينشئ العميل مضبوط الأنواع بشكل تلقائي.
* وضع التطوير لا يزامن: تأكد من أن `yarn twenty app:dev` قيد التشغيل وأن التغييرات ليست متجاهلة من بيئتك.
قناة المساعدة على Discord: https://discord.com/channels/1130383047699738754/1130386664812982322
Some files were not shown because too many files have changed in this diff
Show More
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.