Commit Graph

474 Commits

Author SHA1 Message Date
Félix Malfait 082400f751 Add objectRecordCounts query to /metadata endpoint (#18054)
## Summary

- Adds an `objectRecordCounts` query on the `/metadata` GraphQL endpoint
that returns approximate record counts for all objects in the workspace
- Uses PostgreSQL's `pg_class.reltuples` catalog stats — a single
instant query instead of N `COUNT(*)` table scans
- Replaces the previous `CombinedFindManyRecords` approach which hit the
server's 20 root resolver limit and silently showed 0 for all counts on
the settings Data Model page

### Server
- `ObjectRecordCountDTO` — GraphQL type with `objectNamePlural` and
`totalCount`
- `ObjectRecordCountService` — reads `pg_class` catalog for the
workspace schema
- Query added to `ObjectMetadataResolver` with `@MetadataResolver()` +
`NoPermissionGuard`

### Frontend
- `OBJECT_RECORD_COUNTS` query added to
`object-metadata/graphql/queries.ts`
- `useCombinedGetTotalCount` simplified to a zero-argument hook using
the new query
- `SettingsObjectTable` simplified to a single hook call

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-19 09:02:31 +00:00
Etienne 4cb64c6aa5 Restore old favorite design (#18049)
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>
2026-02-18 21:35:17 +01:00
Abdul Rahman 015ccbf0a7 Navbar customization improvements (#17863)
- Add folder icon editing support
- And other navbar customization fixes/improvements.

---------

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
Co-authored-by: Devessier <baptiste@devessier.fr>
Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
2026-02-17 19:53:52 +00:00
Charles Bochet 310d13fd17 Migrate twenty ui to jotai (#17900)
## 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)
2026-02-12 16:43:34 +01:00
Charles Bochet b456f79167 Reduce leak between gql schema (#17878)
## 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
2026-02-12 10:58:52 +01:00
Abdul Rahman 3fb2352c78 Navbar customization followup (#17848)
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>
2026-02-12 02:35:43 +00:00
Abdul Rahman 0902579fbe Feat: Navbar customization (#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>
2026-02-11 15:33:51 +00:00
Etienne d0c1841f0f File - Migrate avatarUrl > avatarFile on person (data migration + logic) + Attachment data migration (#17752)
- 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
2026-02-11 15:07:05 +00:00
Félix Malfait 177cae8c53 feat: audit Logs (#17660) 2026-02-04 13:30:55 +01:00
Etienne 4fcc424e24 Files field - add files field display and input + filtering (#17637)
This PR introduces a new FILES field type for Twenty CRM, allowing users
to attach multiple files to any record.

- Files field display
- Files field preview
- Files field input
- Files field filtering

To test : 
1/ need to activate feature flag `IS_FILES_FIELD_ENABLED` + create a new
FILES field

- display in read only, edit, inline, table
- edit
- filter
- export


closes https://github.com/twentyhq/core-team-issues/issues/2154


<img width="354" height="134" alt="Screenshot 2026-02-02 at 19 11 01"
src="https://github.com/user-attachments/assets/3c3de89e-f6b6-4526-b710-e4c7ee1a6d30"
/>
<img width="1081" height="933" alt="Screenshot 2026-02-02 at 19 10 41"
src="https://github.com/user-attachments/assets/7c9a7278-edb7-4d4a-882c-f7404689d011"
/>
<img width="1073" height="719" alt="Screenshot 2026-02-02 at 19 10 33"
src="https://github.com/user-attachments/assets/79cc372b-56a2-4cbf-b4fe-023adc4753a8"
/>
2026-02-04 10:55:48 +00:00
Charles Bochet 97867c11e6 Upgrade application model (#17673) 2026-02-03 16:44:00 +01:00
BOHEUS 2bcae523ac Added option to prevent sending requests to twenty-icons (#16723)
Solution for #15891


https://github.com/user-attachments/assets/82ce5c4b-0a78-45a8-8196-413473887820

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
2026-01-30 16:56:22 +01:00
Charles Bochet da6f1bbef3 Rename serverlessFunction to logicFunction (#17494)
## Summary

Rename "Serverless Function" to "Logic Function" across the codebase for
clearer naming.

### Environment Variable Changes

| Old | New |
|-----|-----|
| `SERVERLESS_TYPE` | `LOGIC_FUNCTION_TYPE` |
| `SERVERLESS_LAMBDA_REGION` | `LOGIC_FUNCTION_LAMBDA_REGION` |
| `SERVERLESS_LAMBDA_ROLE` | `LOGIC_FUNCTION_LAMBDA_ROLE` |
| `SERVERLESS_LAMBDA_SUBHOSTING_URL` |
`LOGIC_FUNCTION_LAMBDA_SUBHOSTING_URL` |
| `SERVERLESS_LAMBDA_ACCESS_KEY_ID` |
`LOGIC_FUNCTION_LAMBDA_ACCESS_KEY_ID` |
| `SERVERLESS_LAMBDA_SECRET_ACCESS_KEY` |
`LOGIC_FUNCTION_LAMBDA_SECRET_ACCESS_KEY` |

### Breaking Changes

- Environment variables must be updated in production deployments
- Database migration renames `serverlessFunction` → `logicFunction`
tables
2026-01-28 01:42:19 +01:00
Abdul Rahman 2a8f834377 Integrate NavigationMenuItem with feature flag support (#17268)
## 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
c99746f08b9f84fc8cec4fcc3a7d7afb8ea92db7. 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>
2026-01-27 10:42:27 +00:00
Arun 84d140025a fix: refresh virtualized table when field metadata is updated ( #16388 ) (#17214)
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>
2026-01-24 18:56:00 +00:00
Félix Malfait 41dd9856e6 fix(twenty-front): fix tsconfig to properly typecheck all files with tsgo (#17380)
## 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
2026-01-23 11:22:23 +01:00
Charles Bochet 235fc44228 Fix tests (#17356)
A small fix
2026-01-22 15:35:39 +01:00
Félix Malfait 3ed67b825e feat: implement generic many-to-many junction relation support (#16820)
## Overview

This PR implements **generic many-to-many relation support** through
junction tables (also known as associative entities or join tables).
This replaces the need for hardcoded taskTarget/noteTarget logic and
provides a flexible foundation for modeling complex entity
relationships.

## Architecture

### Data Model

Many-to-many relationships are implemented using a **junction object
pattern**:

```
┌─────────┐       ┌──────────────────┐       ┌─────────┐
│  Pet    │──────>│   PetRocket      │<──────│ Rocket  │
│         │ 1:N   │  (junction)      │  N:1  │         │
│ rockets ├───────┤ pet    : Pet     ├───────┤         │
└─────────┘       │ rocket : Rocket  │       └─────────┘
                  └──────────────────┘
```

The junction object (PetRocket) has:
- A `MANY_TO_ONE` relation to **Pet** (the source)
- A `MANY_TO_ONE` relation to **Rocket** (the target)

The source object (Pet) has a `ONE_TO_MANY` relation pointing to the
junction, with **field settings** that specify which target field to
follow.

### Field Settings Schema

Junction configuration is stored in `FieldMetadataRelationSettings`:

```typescript
{
  relationType: "ONE_TO_MANY",
  // Points to the target field on the junction object
  junctionTargetFieldId?: string;      // For regular relations
  junctionTargetMorphId?: string;      // For polymorphic relations
}
```

**Two configuration modes:**
1. **`junctionTargetFieldId`** - References a specific `RELATION` field
on the junction
2. **`junctionTargetMorphId`** - References a `morphId` group for
polymorphic targets (e.g., link to Person OR Company)

### GraphQL Query Generation

When a junction relation is detected, the GraphQL fields are generated
to fetch the nested target:

```graphql
query GetPetWithRockets {
  pet(id: "...") {
    rockets {           # ONE_TO_MANY to junction
      id
      rocket {          # Target field on junction
        id
        name
        __typename
      }
    }
  }
}
```

For polymorphic junction targets:
```graphql
caretakerPerson { id, name }
caretakerCompany { id, name }
```

## Frontend Architecture

### Display Flow

1. **Detection**: `hasJunctionConfig()` checks if field has junction
settings
2. **Config Resolution**: `getJunctionConfig()` resolves junction object
metadata and target fields
3. **Record Extraction**: `extractTargetRecordsFromJunction()` extracts
target records from junction records
4. **Rendering**: Target records displayed as chips (not junction
records)

### Edit Flow

1. **Picker Opening**: Initializes the multi-record picker with:
   - Searchable object types (derived from junction target fields)
   - Pre-selected items (extracted from existing junction records)
   
2. **Selection Handling**: Manages create/delete of junction records:
   - **Select**: Creates new junction record with source + target IDs
   - **Deselect**: Finds and deletes the junction record
- **Optimistic Updates**: Manually updates Recoil store before API call

### Key Trade-offs

| Decision | Trade-off |
|----------|-----------|
| Junction records managed manually | More control over optimistic
updates, but requires manual cache management |
| Settings stored per-field | Flexible (same junction can power
different views), but requires UI to configure |
| Polymorphic via morphId groups | Supports N target types, but adds
query complexity |
| Feature flag gated | Safe rollout, but requires flag management |

## Backend Changes

- **Validation**: Junction target field must exist and be a valid
`MANY_TO_ONE` relation
- **Settings**: Extended `FieldMetadataRelationSettings` type with
junction fields
- **Dev Seeder**: Added sample junction objects (PetRocket,
EmploymentHistory, PetCareAgreement) for testing

## How to Test

1. Enable the `IS_JUNCTION_RELATIONS_ENABLED` feature flag
2. Create objects with junction pattern (Pet → PetRocket → Rocket)
3. Configure the junction target in field settings (advanced mode)
4. Verify:
- Display shows target objects (Rockets), not junction records
(PetRockets)
   - Picker allows selecting/deselecting targets
   - Changes persist correctly



https://github.com/user-attachments/assets/d04f057a-228c-4de8-af48-76bb2d72cac1

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
2026-01-20 21:58:13 +01:00
Weiko b6635ba272 Add RLS Entitlement check (#17179)
## Context
- Add RLS entitlement to billing
- Check value in the backend (for RLS predicate entity
queries/mutations)
- Expose billingEntitlements to the API inside currentWorkspace to check
available features to the workspace and display the role components
accordingly
- Cleanup RLS when plan changes back to one without RLS.

This should cover almost everything, imho we don't need to check in the
ORM because => We can't create RLS without the correct PLAN and
switching back to a PLAN without RLS deletes existing RLS through
stripes webhooks
2026-01-20 16:06:37 +00:00
Weiko 8413c6f3dd Fix insert new record with RLS (#17164)
## Context
Now that RLS predicates are applied, creating a record through the FE
(which is empty by default) is failing if your role has predicates and
your input does not respect them (which will always be true since, as
said above, input will be pretty much empty)

## Implementation
- Moved isMatching* filters to twenty-shared
- Implemented isMatchingRlsPredicates utils in the backend (ORM) to
check before insertion/update if the record is matching the current user
role Rls predicates, reusing the isMatching* filters utils moved to
twenty-shared
- Frontend now applies RLS predicates before creating a new record
(similarly to what we do with view filters)

Note:
It seems composite were not properly handled with view-filter insertion
logic, since I'm reusing the util for now, the issue remains for RLS and
will need to be addressed
2026-01-15 16:40:47 +00:00
Raphaël Bosi 869a0a7cf4 Hardcode workflow objects and dashboards to be last in the navigation drawer (#17123)
Closes https://github.com/twentyhq/core-team-issues/issues/2058

We settled on a simpler solution by just hardcoding the objects order
for now. We will change this once it's possible to reorder the workspace
favorites with drag and drop.
2026-01-13 16:24:14 +00:00
Thomas Trompette 3acc87a620 [SSE] Add backend for SSE subscriptions (#17022)
- moved a few gql types to twenty shared to re-use in server
- added a new endpoint, onEventSubscription that expect a streamId to
create a connection
- two new endpoints to store queries in Redis
- updated the existing batch channel to directly use object record type
2026-01-10 14:44:02 +00:00
Félix Malfait 1a5675d63e feat: add sorting on relation fields (Many-to-One) (#17021)
## Summary

This PR enables sorting records by fields of related objects. For
example, sorting **People by their Company's name**.

### Before
Only scalar and composite fields could be sorted. Relation fields showed
in the sort dropdown but produced errors.

### After
Many-to-One relation fields can now be sorted using the related object's
**label identifier field** (e.g., Company's `name`).

---

## Changes

### Frontend
- Added `RELATION` to sortable field types (restricted to `MANY_TO_ONE`
relations)
- New `getOrderByForRelationField()` generates nested orderBy structures
using the related object's label identifier
- Updated `turnSortsIntoOrderBy()` to handle relation fields by looking
up related object metadata

### Backend
- Extended `GraphqlQueryOrderFieldParser.parse()` to detect nested
relation ordering like `{ company: { name: 'AscNullsLast' } }`
- Returns `ParseOrderByResult` containing both `orderBy` conditions and
`relationJoins` info
- Added LEFT JOINs for relation ordering in `applyOrderToBuilder()`
- Added `addRelationOrderColumnsToBuilder()` for TypeORM DISTINCT
compatibility

### Tests
- Added unit tests for `filterSortableFieldMetadataItems`,
`getOrderByForRelationField`, and `turnSortsIntoOrderBy`
- Added integration tests covering ascending/descending order and
composite label identifiers

---

## TypeORM Bug Workaround

We encountered a significant TypeORM limitation when implementing this
feature. When using `getMany()` with `ORDER BY` on joined relation
columns, TypeORM generates a DISTINCT subquery that has specific
requirements:

### Issue 1: Alias Parsing
TypeORM's `orderBy()` method fails with **"alias not found"** when using
quoted SQL identifiers like `"company"."name"`. TypeORM internally
expects unquoted property paths (e.g., `company.name`) for its alias
resolution mechanism.

### Issue 2: setFindOptions Clears addSelect
`setFindOptions({ select })` **clears any previously added `addSelect()`
columns**. This caused `"column distinctAlias.company_name does not
exist"` errors because the relation columns needed for ORDER BY were
being removed.

### Solution
We split the logic into two methods:
1. `applyOrderToBuilder()` - adds JOINs and ORDER BY (before
`setFindOptions`)
2. `addRelationOrderColumnsToBuilder()` - adds relation columns for
SELECT (AFTER `setFindOptions`)

This ensures the relation columns are present in the final SQL query's
SELECT clause with the proper underscore aliases (`company_name`) that
TypeORM's DISTINCT subquery expects.

**Related TypeORM issue**:
https://github.com/typeorm/typeorm/issues/9921

---

## Screenshots/Demo

_Add screenshots if applicable_
2026-01-10 11:02:16 +01:00
Raphaël Bosi 4faed25624 [PAGE LAYOUTS] Add widgets validation (#16635)
- Add widget validation
- Remove 'None' option for primary axis group by
- Fix error message parsing by passing the operation type in
`useMetadataErrorHandler`
2026-01-07 15:00:13 +00:00
Paul Rastoin 1128331cc1 Fix field item type tag (#16860)
# Introduction
This has been fixed on main already 1 hour ago, this PR now only passes
the field application id instead of the object applicationId that could
be different for example when creating a custom field on a standard
object

For the moment not introducing any logic around application integrity
directly and still relying on the isCustom and standardId definition
This will have to be refactored once we deprecate the standardId
2025-12-30 15:02:07 +00:00
Félix Malfait b27a97f2c5 feat: enforce @/ alias for imports and fix all relative parent imports (#16787)
## Summary
This PR enforces the use of `@/` alias for imports instead of relative
parent imports (`../`).

## Changes

### ESLint Configuration
- Added `no-restricted-imports` pattern in `eslint.config.react.mjs` to
block `../*` imports with the message "Relative parent imports are not
allowed. Use @/ alias instead."
- Removed the non-working `import/no-relative-parent-imports` rule
(doesn't work properly in ESLint flat config)

### VS Code Settings
- Added `javascript.preferences.importModuleSpecifier: non-relative` to
`.vscode/settings.json` (TypeScript setting was already there)

### Code Fixes
- Fixed **941 relative parent imports** across **706 files** in
`packages/twenty-front`
- All `../` imports converted to use `@/` alias

## Why
- Consistent import style across the codebase
- Easier to move files without breaking imports
- Better IDE support for auto-imports
- Clearer understanding of where imports come from
2025-12-23 22:57:51 +01:00
neo773 024ee152a0 Add useUpdateManyRecords hook and message folders sync status mutation (#16694)
- Added new `useUpdateManyRecords` hook for batch record updates with
optimistic cache updates
- Added `updateMessageFoldersSyncStatus` hook for managing message
folder sync state
- Redesigned Message Folders List with BreadCrumb and Animations
2025-12-19 17:22:59 +01:00
Félix Malfait 5df8fd90c3 feat: simplify AI chat architecture and add record links (#16463)
## Summary

This PR significantly simplifies the AI chat architecture by removing
complex routing/planning mechanisms and introduces clickable record
links in AI responses.

## Changes

### AI Chat Architecture Simplification
- **Removed** the entire `ai-chat-router` module (~850 lines) including:
  - Strategy decider service
  - Plan generator service
  - Complex routing logic
- **Removed** agent execution planning services (~700 lines):
  - `agent-execution.service.ts`
  - `agent-plan-executor.service.ts`
  - `agent-tool-generator.service.ts`
- **Added** centralized `ToolRegistryService` for tool management:
  - Builds searchable tool index (database, action, workflow tools)
  - Provides tool lookup by name
  - Supports agent search for loading expertise
- **Added** `ChatExecutionService` as simple replacement:
  - Includes full tool catalog in system prompt
- Pre-loads common tools (find/create/update for company, person,
opportunity, task, note)
  - Uses `load_tools` mechanism for dynamic tool activation
  - Enables native web search by default

### Record References in AI Responses
- Added `recordReferences` field to tool outputs for create, find, and
update operations
- Implemented `[[record:objectName:recordId:displayName]]` syntax for AI
to reference records
- Created `RecordLink` component that renders clickable chips with
object icons
- Integrated record link parsing into the markdown renderer
- Users can now click directly on created/found records in AI responses

### Workflow Agent Fixes
- Fixed cache invalidation issue when creating agents in workflows
- Added default prompt for workflow-created agents to prevent validation
errors
- Relaxed agent validation to only check properties being updated (not
all required properties)

### Code Quality Improvements
- Extracted `getRecordDisplayName` utility that mirrors frontend's
`getLabelIdentifierFieldValue` logic
- Uses object metadata to determine the correct label identifier field
- Handles `FULL_NAME` composite type for person/workspaceMember objects
- Shared across create, find, and update record services

## Net Impact
- **~1,200 lines deleted** (complex routing/planning code)
- **~500 lines added** (simpler tool registry + record links)
- Significantly reduced code complexity
- Better tool discovery through full catalog in system prompt
- Improved UX with clickable record references

## Testing
- Typecheck passes
- Lint passes
- Manual testing of AI chat with record creation and linking
2025-12-10 15:14:12 +01:00
Abhishek Kumar 6fe3d586cd fix(workflow): clicking on 'See runs' shows executions from all workflows (#16300)
Fixes: #16229 and #16286

This PR fixes the bug where click on 'See All Runs' button for a
specific workflow was not filtering the runs for that speciific
workflow.

**Reason** : The filter logic in `getFilterFilterableFieldMetadataItems`
was excluding ALL system fields(except field id) from being filterable,
which prevented these essential business relations from being used in
filters.

**Fix** : Added a targeted exception to allow specific system relation
fields (`workflow` and `workflowVersion`) to be filterable, similar to
how the `id` system field is already whitelisted.


### Before



https://github.com/user-attachments/assets/50b98626-ccdc-468a-96f9-0c911fec9a39

<img width="1511" height="587" alt="Screenshot 2025-12-03 at 11 52
50 PM"
src="https://github.com/user-attachments/assets/db6ea12b-8441-41c0-8a53-b9786b5c543a"
/>

### After



https://github.com/user-attachments/assets/856bbd61-4958-43de-8fa5-3f52ca9e7260



<img width="835" height="562" alt="Screenshot 2025-12-04 at 12 33 32 AM"
src="https://github.com/user-attachments/assets/bdcb43c7-d4ce-4932-8317-f0736d880784"
/>
2025-12-03 20:46:41 +01:00
Abdullah. ee08060798 Improve deactivated objects & fields behaviors. (#16090)
Closes [1918](https://github.com/twentyhq/core-team-issues/issues/1918).

- For the first point in the issue, we just show the deactivated entries
along with the deactivated text.

---

- For the second point, we show a banner and control the
enabled/disabled state of save button depending on whether we're
allowing the user to create table with the typed name.
- For example, we do not want to allow the user to create a table with
reserved name, so we disable the save button without showing a banner.
- Similarly, we do not want the user to create a table with a name that
already exists in the database. In this case, we show a banner and we
also disable the save button.
- Finally, we do not want to allow the user to create a table where
singular and plural name are the same. Therefore, we disable the save
button for names like `works`.

---

- For the third point, if we add the delete button, it logically means
that we allow the user to delete a custom object/field even it has not
been deactivated yet, so did that.
- Upon deleting the object/field, if we wait for the metadata to refetch
before we navigate, this is what we see because the path does not exist
any longer after deletion and we're waiting for refetch on the path
until we navigate away.


https://github.com/user-attachments/assets/dbe0569c-db88-4285-851f-22551b1ca81e

- To avoid this page from appearing, I replaced awaiting refetch to not
awaiting refetch and redirecting while the refetch happens in the
background.
- Therefore, when we delete something, there is a slight delay for when
it is actually cleared out from the list, but the Not Found view does
not appear on the screen.


https://github.com/user-attachments/assets/47f49579-ce51-4d6a-b857-72046247bb4b

- I tried optimistically removing the object/field from the metadata,
but it leads to some issues (crashes the app) and I have not been able
to find a solution for it yet.
- Therefore, instead of getting stuck at perfection and blocking myself,
I stopped getting into the issue further and created this PR by ensuring
that the desired functionality works.


<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> Display deactivated objects/fields by default, add delete actions with
confirmation, and unify metadata name computation (auto-suffix reserved
keywords) across front/back with conflict checks in object creation.
> 
> - **Frontend (Settings/Data Model)**:
> - **Visibility/UX**: Show `Deactivated` labels for objects/fields;
filters default to include inactive (`showDeactivated`/`showInactive`
true); replace field action dropdown with chevron link.
> - **Delete flows**: Add delete buttons for custom objects/fields with
confirmation modals and background refetch to avoid Not Found flashes.
> - **Creation/Edit validation**: Add name conflict detection banner in
`SettingsDataModelObjectAboutForm` and disable Save on conflicts;
simplify `metadataLabelSchema` to use computed name; form fields
validate on change and sync API names.
> - **Shared (twenty-shared/metadata)**:
> - Add `computeMetadataNameFromLabel` util (slugify+camelCase) and
`RESERVED_METADATA_NAME_KEYWORDS`; auto-append `Custom` to reserved
names; export constants/utilities.
> - **Backend**:
> - Migrate to shared `computeMetadataNameFromLabel`; update validators
to use shared reserved keywords with new messages; allow deletion of
active custom fields/objects (keep standard guards); adjust
services/decorators accordingly.
> - **Tests/Stories**:
> - Update unit/integration snapshots for new reserved-name messages and
behaviors; add missing i18n/router decorators in stories.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
5b126155606f6dbc8f7f91e2192cffb7bd2ebd2c. 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>
2025-12-01 15:04:36 +00:00
Félix Malfait e7ebf51e50 Replace agent handoff system with planning-based router (#16003)
## Overview

This PR replaces the dynamic agent handoff system with a more
predictable planning-based router that decides upfront how to handle
multi-agent coordination.

## Major Changes

### 🔄 Architecture Shift: Handoffs → Planning

**Removed:**
- `AgentHandoffEntity` and handoff tracking system
- `AgentHandoffService` and `AgentHandoffExecutorService`
- Dynamic agent-to-agent transfers during execution
- Handoff tool generation and description templates

**Added:**
- `AiRouterService` with two strategies: `simple` (single agent) and
`planned` (multi-agent)
- `AgentPlanExecutorService` for executing multi-step plans
- Plan validation (cycle detection, dependency resolution)
- `UnifiedRouterResult` type with discriminated union

### 🤖 New Standard Agents

Added two new specialized agents:
- **Researcher Agent**: Web search, fact-finding, competitive
intelligence
- **Code Agent**: TypeScript function generation for serverless
workflows

### 🏗️ Router Refactoring (Latest)

Split router responsibilities into focused services:
- `AiRouterStrategyDeciderService`: Decides simple vs planned strategy
- `AiRouterPlanGeneratorService`: Generates and validates execution
plans
- `AiRouterService`: Coordinates between services (reduced from 426→275
lines)

### ⚙️ Configuration Improvements

- Added `outputStrategy` to agent definitions (`direct` vs `synthesize`)
- Removed hardcoded special cases for workflow-builder
- Added `plannerModel` field to workspace entity
- Increased `MAX_STEPS` from 10 to 25 for complex workflows

### 📝 Agent Prompt Refinements

Significantly simplified prompts for better clarity:
- Workflow Builder: 51→36 lines
- Helper: 49→28 lines
- Data Manipulator: Enhanced with sorting guidance

### 🔍 Enhanced Debugging

- Plan reasoning and step count in data message parts
- Router debug info with token usage tracking
- Better logging throughout execution pipeline

## Benefits

1. **Simpler Mental Model**: Router decides upfront vs dynamic transfers
2. **Better Predictability**: Users see the plan before execution
3. **Cleaner Architecture**: SRP with focused services
4. **Configuration Over Code**: Agent behavior via config, not hardcoded
logic
5. **Plan Validation**: Catches invalid dependencies and cycles

## Migration Notes

- Database migration removes `agentHandoff` table
- Adds `plannerModel` column to workspace table
- No API breaking changes (agent endpoints unchanged)

## Testing

- Integration tests updated to remove handoff dependencies
- Agent tool test utilities simplified
- Plan validation covered by new logic

## Next Steps (Future PRs)

- Parallel execution of independent plan steps
- Dynamic re-planning based on results
- Plan caching for common routing patterns
- Error recovery strategies in plan executor
2025-11-25 12:10:14 +01:00
Paul Rastoin 8299488f21 Fix front data model edition + non nullable workspaceCustom application migration (#16016)
# Introduction
Two things:
- Enforcing non nullable workspace custom application Id for any
workspace
- Fixing front non editable data models following
https://github.com/twentyhq/twenty/pull/15911 that associate any custom
entities to an applicationId. The front was putting everything as
readonly when under an app ( we will have to handle the twenty standard
application in the future too )

## Fallback
### Migration
The non nullable migration will fail when released, that's why it's
being swallowed and re-run in an upgrade command post workspace custom
application creation for those that miss one. Allowing the migration to
pass in the end
The typeorm migration still need to exists for any new workspaces

### GetCurrentUser
In order to dynamically display isReadOnly in data model settings we're
fetching the workspaceCustomApplicationId through the `getCurrentUser`
If not fallback this endpoint would throw until we're handling existing
workspaces that do not have a custom workspace application
The fallback should be removed post release
2025-11-24 13:39:04 +00:00
Lucas Bordeau 061cc897af Improve record group aggregate query performance (#15828)
This PR is a first step for improving the performance on boards and
table with groups.

It is related to :
https://github.com/twentyhq/core-team-issues/issues/1870

Here we implement only a groupBy query for aggregate values in the group
section.

This also allows to improve the DX of aggregate computing and group by
query creation and parsing.

## Demo 

Main : 



https://github.com/user-attachments/assets/5d2a8077-5322-4928-a551-f03583bcfb87



This PR : 



https://github.com/user-attachments/assets/d0e82b28-72c3-40f0-b5cb-045f1a736ffb



## Aggregate update bug fix

This PR also solves a bug with aggregate update that was already present
on main.

The bug is linked to core views not being updated properly during a
modification of the aggregate operation on a view.

We should probably improve the view lifecycle and state management
because it is a bit too complex right now.

Main : 


https://github.com/user-attachments/assets/10dbfb8b-dfa0-4f21-8698-d222871a43e7

This PR : 


https://github.com/user-attachments/assets/bac41890-5191-4e4c-b82b-19b1039e9ab5

## Miscellaneous 

- Fixed optimistic rendering of group by queries, when adding a new
record, the aggregate recomputes well.

## TODO 

- We might want to improve the optimistic for group by queries that
don't have records nor more than one dimension.
2025-11-23 20:40:23 +01:00
Félix Malfait 0fa2a4524a feat: Add filter button to objects table and enable system objects access (#15856)
## Summary
This PR replaces the Active/Inactive accordion sections with a modern
filter dropdown button and enables full access to system objects in
advanced mode.

## Changes

### UI Improvements
-  Replaced accordion sections with a filter button dropdown (matching
the design pattern from the Group filter)
-  Added 'Deactivated' toggle filter (hidden by default, uses
IconArchive)
-  Added 'System objects' toggle filter (only visible in advanced mode,
uses IconSettings)
-  Fixed search input width to properly fill available space
-  Proper button sizing and alignment

### System Objects Support
-  Made system objects visible when 'System objects' filter is toggled
on
-  System objects are now fully clickable and accessible
-  Updated object detail page to support system objects
-  Updated field creation/edit pages to support system objects
-  System objects can now have custom fields added

### Architecture
-  Implemented scalable filter architecture using a single filtered
list
-  Easy to add more filters in the future (e.g., show remote objects)
-  All filters work independently and can be combined

## Testing
- [x] Tested deactivated objects toggle
- [x] Tested system objects toggle (only shows in advanced mode)
- [x] Tested clicking on system objects
- [x] Tested adding custom fields to system objects
- [x] No linter errors

## Screenshots
See attached screenshots in the conversation for the new filter UI.

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> Adds a filter dropdown to the Settings Objects table (incl.
deactivated/system toggles), enables system objects across
settings/field flows, seeds core views (workspace members, messages,
threads, calendar events), and adds actions for Workspace Members.
> 
> - **Settings UI**
> - **Objects Table**: Replaces Active/Inactive sections with a single
filterable list and dropdown (`Deactivated`, `System objects` in
advanced mode); updates props to `objectMetadataItems` and removes
accordion sections.
> - **Search/UX**: Search input fills available space; inactive rows
show activation/delete menu; active rows remain navigable.
> - **Pages Updated**: `SettingsObjects`,
`SettingsApplicationDetailContentTab` switch to new table API; object
detail and new-field flows use `findObjectMetadataItemByNamePlural`
(works with system objects).
> - **Action Menu**
> - **Workspace Members**: Adds `WORKSPACE_MEMBERS_ACTIONS_CONFIG` with
"Manage members in settings" action; wired into `getActionConfig` for
`WorkspaceMember`.
> - **Server (Core Views Seed)**
> - Adds default views: `workspaceMembersAllView`, `messagesAllView`,
`messageThreadsAllView`, `calendarEventsAllView`; included in
`prefillCoreViews`.
> - Marks workflow entities as system (`WorkflowRun`,
`WorkflowVersion`).
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
6a2856df85104665bd986cc63f2d54f19bf668d0. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
2025-11-17 19:10:19 +01:00
Charles Bochet 7099b1c174 Fix export CSV not using right columns (#15752)
Fixes https://github.com/twentyhq/twenty/issues/15575
2025-11-10 22:00:39 +01:00
nitin 636c564168 Add shortcut on dashboard workspace entity + add shortcuts (#15603)
syncmetadata required!
closes
https://discord.com/channels/1130383047699738754/1435260594176393256

---------

Co-authored-by: bosiraphael <raphael.bosi@gmail.com>
Co-authored-by: Raphaël Bosi <71827178+bosiraphael@users.noreply.github.com>
2025-11-05 13:42:37 +00:00
Abdullah. 5b2950c43a Introduce SSO bypass permission. (#15417)
Closes [Core Issue
#1772](https://github.com/twentyhq/core-team-issues/issues/1772).

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> Introduces SSO bypass with a new permission flag and workspace-level
provider toggles, enabling permitted users to log in via
Google/Microsoft/Password when SSO-only, with backend enforcement and
frontend UI/hooks/queries.
> 
> - **Backend**:
> - **Permission & Enforcement**: Add `PermissionFlagType.SSO_BYPASS`;
update `AuthService` to allow login via non-SSO providers when workspace
bypass is enabled and user has `SSO_BYPASS`.
> - **Workspace Model**: Add `isGoogleAuthBypassEnabled`,
`isMicrosoftAuthBypassEnabled`, `isPasswordAuthBypassEnabled`
(migration, entity, update input, service validation).
> - **Public API**: Extend `PublicWorkspaceDataOutput` with
`authBypassProviders`; resolver computes it; permissions defaults
include `SSO_BYPASS`.
> - **Frontend**:
> - **GraphQL/State**: Generate new types/fields; add
`authBypassProviders` to `GetPublicWorkspaceDataByDomain`; new states
`workspaceAuthBypassProvidersState`, `workspaceBypassModeState`.
> - **Auth UI/Logic**: Add `useWorkspaceBypass`; update sign-in form and
footer to offer "Bypass SSO" and use merged providers when enabled;
remove auto-redirect when single SSO.
> - **Settings**: Add Security section to toggle bypass methods per
provider; conditionally show Change Password via `useCanChangePassword`.
> - **Tests/Mocks**: Update mocks and tests to include bypass
flags/providers.
> 
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
8c393b2bad387fb6e8b8f40027f8637dd6e85723. 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>
2025-11-03 11:40:09 +01:00
Ranjeet Baraik 0cbd1e8ec5 fix: login redirection to active objects (#15366)
Fixes - https://github.com/twentyhq/twenty/issues/15364

- Removed `activeNonSystemObjectMetadataItems` from the hook as it was
not necessary. Hook use `alphaSortedActiveNonSystemObjectMetadataItems`
now.
- Correctly check for read permissions.

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
2025-10-30 17:31:26 +01:00
Paul Rastoin ab24cae2eb Front references to views as records (#15425) 2025-10-29 12:23:49 +01:00
Paul Rastoin 503a5029da Refactor twenty-front metadata api services for v2 (#15360)
# Introduction
Please first review this PR initial base
https://github.com/twentyhq/twenty/pull/15358
In a nutshell refactored the frontend fetchers to display v2 errors
format smoothly
Please note that the v2 now finished the whole validation and does fail
fast anymore ( summary is hardcoded for the moment )
```json
[
  {
    "extensions": {
      "code": "BAD_USER_INPUT",
      "errors": {
        "cronTrigger": [],
        "databaseEventTrigger": [],
        "fieldMetadata": [
          {
            "errors": [
              {
                "code": "INVALID_FIELD_INPUT",
                "message": "Default value should be as quoted string",
                "value": "",
              },
              {
                "code": "INVALID_FIELD_INPUT",
                "message": "Default value "" must be one of the option values",
                "value": "",
              },
            ],
            "flatEntityMinimalInformation": {
              "id": Any<String>,
              "name": "testField",
              "objectMetadataId": Any<String>,
            },
            "status": "fail",
            "type": "create_field",
          },
        ],
        "index": [],
        "objectMetadata": [],
        "routeTrigger": [],
        "serverlessFunction": [],
        "view": [],
        "viewField": [],
        "viewFilter": [],
        "viewGroup": [],
      },
      "message": "Validation failed for 0 object(s) and 0 field(s)",
      "summary": {
        "invalidCronTrigger": 0,
        "invalidDatabaseEventTrigger": 0,
        "invalidFieldMetadata": 0,
        "invalidIndex": 0,
        "invalidObjectMetadata": 0,
        "invalidRouteTrigger": 0,
        "invalidServerlessFunction": 0,
        "invalidView": 0,
        "invalidViewField": 0,
        "invalidViewFilter": 0,
        "invalidViewGroup": 0,
        "totalErrors": 0,
      },
      "userFriendlyMessage": "Validation failed for 0 object(s) and 0 field(s)",
    },
    "message": "Multiple validation errors occurred while creating fields",
    "name": "GraphQLError",
  },
]
```

## What's done
- `usePersistView` tool ( CRUD )
- renamed `usePersistViewX` tools accordingly ( no more records or core
)
- Now catching a lot of before unhandled exceptions
- refactored each services to handle their own exception handlers and
return either the response or the error within a discriminated union
record

## Result
### Primary entity error 
When performing an metadata operation on a given metadata, if validation
errors occurs we will display each of them in a toast
Here while creating an object metadata.
<img width="700" height="327" alt="image"
src="https://github.com/user-attachments/assets/0c33d13c-c66c-4749-af36-b253abd3449b"
/>

### Related entity error
Still while creating an object
<img width="700" height="327" alt="image"
src="https://github.com/user-attachments/assets/52607788-c4e9-470c-ac8c-23437345ee5c"
/>

### Translated
<img width="700" height="327" alt="image"
src="https://github.com/user-attachments/assets/a7198c20-ae82-47a6-910c-761de9594672"
/>


## Conclusion
This PR is an extract of https://github.com/twentyhq/twenty/pull/15331
close https://github.com/twentyhq/core-team-issues/issues/1776
## Notes
- Not refactor around triggers services as they're not consumed directly
by any frontend services
2025-10-28 10:50:41 +01:00
Abdul Rahman 32558673c6 feat: Implement AI Router for Dynamic Agent Selection (#15227)
Adds intelligent routing system that automatically selects the best
agent for user queries based on conversation context.

### Changes:
- Added `routerModel` column to workspace table for configurable router
LLM selection
- Implemented `RouterService` with conversation history analysis and
agent matching logic
- Created router settings UI in AI Settings page with model dropdown
- Removed agent-specific thread associations - threads are now
agent-agnostic
- Added real-time routing status notification in chat UI with shimmer
effect
- Removed automatic default assistant agent creation
- Renamed GraphQL operations from agent-specific to generic (e.g.,
`agentChatThreads` → `chatThreads`)

---------

Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
Co-authored-by: Félix Malfait <felix@twenty.com>
2025-10-22 15:02:41 +02:00
martmull b16ab1b7c9 1518 extensibility front add an application section in settings (#15056)
Protected by IS_APPLICATION_ENABLED featureFlag

Add `Application` section in settings

<img width="301" height="137" alt="image"
src="https://github.com/user-attachments/assets/ee53bdd2-36f6-45c6-8646-17b1e08abf00"
/>


A `settings/applications` route listing all installed applications

<img width="661" height="428" alt="image"
src="https://github.com/user-attachments/assets/69d534c4-4e9e-452a-a3d9-ded0223bb457"
/>

Introduce a new Tag for application managed items

<img width="885" height="759" alt="image"
src="https://github.com/user-attachments/assets/19767be5-61e5-4bd2-a51d-54ed9bfb1923"
/>



A `settings/applications/<application_id>` details setting page listing
all objects, serverlessFunctions and agents created by the application:

<img width="917" height="778" alt="image"
src="https://github.com/user-attachments/assets/7fc056a6-1d73-4242-b2eb-6f8955d8597d"
/>

A `settings/applications/<application_id>/<serverless_function_id>`

<img width="905" height="652" alt="image"
src="https://github.com/user-attachments/assets/56ca0021-26bf-42cb-9abf-34879f16050a"
/>

Add trigger tab in serverless function details (readonly for now)

<img width="899" height="724" alt="image"
src="https://github.com/user-attachments/assets/5eeefa35-f2a4-4fd8-a640-7b5c5891f226"
/>

Set object, serverless and agent setting detail pages readonly for
managed items
<img width="1075" height="859" alt="image"
src="https://github.com/user-attachments/assets/57c73d69-4980-47a2-b752-8dc5ab494530"
/>
<img width="648" height="582" alt="image"
src="https://github.com/user-attachments/assets/5ad5f3f7-3bc3-4e40-870a-4981c6492524"
/>
<img width="982" height="692" alt="image"
src="https://github.com/user-attachments/assets/7ad756c4-5d33-4a0a-9eb8-416c040362b9"
/>
<img width="1077" height="647" alt="image"
src="https://github.com/user-attachments/assets/e086b9f5-4062-4d10-82a9-4023de3cad3f"
/>
2025-10-15 17:13:29 +02:00
Abdullah. d750df7fff Automatically clean up soft-deleted records after X days. (#14862)
Closes #14726

### Added
- `trashRetentionDays` field to workspace entity (default: 14 days)  
- Automated trash cleanup using BullMQ jobs  
- Daily cron (00:10 UTC) that enqueues cleanup jobs for all active
workspaces
- Per-workspace limit: 100k records deleted per day  
- Calendar-based retention: records deleted on day X are cleaned up X+14
days later (at midnight UTC boundaries)

### Architecture
- **Cron (WorkspaceTrashCleanupCronJob):** Runs daily, enqueues jobs in
parallel for all workspaces
- **Job (WorkspaceTrashCleanupJob):** Processes individual workspace
cleanup
- **Service (WorkspaceTrashCleanupService):** Discovers tables with
`deletedAt`, deletes old records with quota enforcement
- **Command:** `npx nx run twenty-server:command
cron:workspace:cleanup-trash` to register the cron

### Testing
- Unit tests for service with 100% coverage of public API  
- Tested quota enforcement, error handling, and edge cases

---------

Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
2025-10-14 18:49:40 +02:00
Guillim 448091e81f Optimistic rendering of morph relations (#14997)
The optimistic rendering of a morph relation Is broken. We intend to fix
it here.

- mapping of gql fields


Fixes https://github.com/twentyhq/core-team-issues/issues/1322

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
2025-10-10 19:11:00 +02:00
Félix Malfait 938debd83e Refactor record layouts for backend-driven configuration (#15021)
## Refactor: Prepare frontend record layouts for backend-driven
configuration

This PR simplifies and prepares the frontend record layout system for
eventual migration to backend-driven layouts, aligning the architecture
with the existing PageLayout system used for dashboards.

### Key Changes

**Architecture Improvements:**
- Created unified LayoutRenderingContext that works for both record
pages (with targetRecord) and dashboards (standalone)
- Replaced prop drilling with context-based data flow - cards access
targetRecord and isInRightDrawer via context hooks
- Introduced useTargetRecord() helper hook that provides type-safe
access to the current record
- Created generic CardRenderer component that handles configuration
injection and context guards uniformly

**Configuration System:**
- Converted all tab icons from React components to JSON-serializable
strings (e.g., Icon: IconCheckbox → icon: 'IconCheckbox')
- Added configuration field to cards, matching the widget configuration
pattern on the backend
- Created CardConfiguration types system similar to WidgetConfiguration
on the backend
- Moved widget-specific props (like showDuplicatesSection) into
configuration objects

**Code Organization:**
- Extracted visibility evaluation logic into reusable
evaluateTabVisibility() utility
- Organized layouts into dedicated files (one per object:
base-record-layout.ts, company-record-layout.ts, etc.)
- Renamed components to match their purpose: Notes → NotesCard,
Attachments → FilesCard, etc.
- Consolidated card rendering from registry object to direct
getCardComponent() function

**API Alignment:**
- Made ifNoReadPermissionObject an explicit part of TabVisibilityConfig
(follows if* naming convention)
- Removed redundant targetObjectNameSingular from tab-level (now derived
from visibility config)
- Card API now mirrors Widget API (both use type, configuration,
accessed via context)
2025-10-10 16:26:17 +02:00
Charles Bochet 37473175a0 Reduce relation loading overload on FE graphql queries (#14991)
## Problem

With Twenty usage growing, we are facing challenges on server side to
respond to the demand. The current bottleneck we are facing is server
CPU.
While investigating CPU performances, we figured out that loading all
relation fields was the biggest issue.

### About graphql query response size

**Example:** on `People Index` Table page:
we query: `person.company` and we query **all non relation fields** on
company relation field.
`{ person { company { id, name, domainName, employees, address ... } }`
 
 
However we only need **company.id, company.name and company.domainName**
to be able to render the Company Chip in the company column (RELATION)
in the table.
`{ person { company { id, name, domainName } }`

We initially assumed that the querying all non relation fields was not
an issue because it was not adding additional load on database (which is
also partially true: if a field is containing a huge json this will add
load on postgres as data needs to be transfered)
This assumption is wrong on CPU side:
- when loading one to many relations (company.people), this quickly ads
up and we end up with response of 20kB quite quickly, even without
adding any custom field on person.
- even when loading a many to one relation, if the user is storing big
data in a given field (note.body for instance, or workflowRun.state),
this starts also being an issue.
- Worst case scenario are starting to happen: on a production workspace
with 20 active workflows. Loading workflow table while displaying
workflow runs commands (20 workflows x 60 workflowRuns x
workflowRun.state which is a big JSON) result in a response of... 125MB.

### Why is it an issue?

When yoga (graphql engine) parses a response to send it back in server
response, it's using `JSON.parse `(or stringify depending on the case).
Parsing data is CPU instensive (as you need to validate, transform) and
this is more or less `O(n)`.
This means returning 125MB is very intense on the CPU and will likely
use 100% of the CPU for ~1sec in our production servers.

NodeJs (nodeV8) is single threaded. It's able to process multiple
requests in parallel but to do that, it will cut them in "microTasks"
and process each microTasks (that can belong to different requests) one
after the other. Exactly like a single threaded CPU would allocate some
time to a process and then to the next one, etc.

This means that this big response request will actually block the other
request.
As a result, all requests are slow and our infrastructure is multi
tenant so some customers are impacting others.

This is even a bigger issue when our health checks start to fail and
containers are starting being considered as unhealthy and killed by the
orchestrator


## How to fix this

1. On Front end side, we should only query what we need. In this PR, I'm
forcing the relations to only query: `id`, `labelIdentifier`,
`imageIdentifier`
2. (later) as we are API first, we also need to do something to protect
the servers from this side too. To do that, Graphql APIs can associate a
cost to each graphql field (maybe 1 for a test fixed, 2 for a JSON
field, 10 for relations, etc...) and we can throttle based on that.

## Changes in this PR

It's mainly about refactor the tooling to generate `RecordGqlFields`
(`generateDepthOneGqlFields`). this tooling is used to generate the list
of fields we want to query. Most of the time we want to query a record
with one level of nesting.
1) Refactor to remove duplicate code => `generateDepthOne` become
`generateDepth` and can handle both `depth = 0 | 1`

2) Introduce `generateDepthRecordGqlFieldsFromFields.ts` which is the
base and can give you a list of gqlFields based on FieldMetadataItems

3) Introduce `generateDepthRecordGqlFieldsFromObject` which is a
shortcut for an object
4) Introduce `generateDepthRecordGqlFieldsFromRecords` which is the
intersection between `generateDepthRecordGqlFieldsFromObject` and a
given record (useful for cache tooling)
5) Replace all usages + introduce a hook
`useGenerateDepthRecordGqlFieldsFromObject` to ease usage
2025-10-10 14:31:12 +02:00
Ashwin Temkar 1d09cb949a Feat/multivalue limit (#14961)
## Summary
1. This change introduces the ability to limit how many values a
multi-value field can contain (for example: maximum number of emails,
phone numbers, links, or array items).
2. It centralizes the limit as a shared constant and type, validates
settings on the server, updates front-end types and components to
consume the setting, and adds a small settings UI so workspace admins
can change the limit per field.
3. Default behavior is preserved: if no max is configured, the existing
default (10) is used.


## **Fixes Issue:** [#14740
](https://github.com/twentyhq/twenty/issues/14740)

## Manual Test Screenshot
<img width="383" height="451" alt="Screenshot 2025-10-08 002413"
src="https://github.com/user-attachments/assets/a7704af6-10ef-4d10-b8c9-a9eeca03bfd9"
/>


### Values in fields


https://github.com/user-attachments/assets/7f59c4f7-3aca-4f83-8c04-3d44988316e3


Let me know if any changes are needed

---------

Co-authored-by: Félix Malfait <felix@twenty.com>
Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
2025-10-08 12:09:38 +00:00
Charles Bochet 84f1559832 Refactor SingleRecordPicker to handle morphs (#14956)
## Scope

- Refactor SingleRecordPicker to handle morphItem and match Multiple one
- fix query generation to take morph into account


Left: 
- double check SingleRecordPicker in Workflows / Form
- Fix tests / stories
2025-10-07 20:07:27 +02:00
Guillim f60817a1e1 Morph many to one picker (#14155)
This PR follows the multiSelect PR merged previously. It will enable
morph relation Many to One to be handled from the table, using a
singleSelect picker

Main point : I decided to change the singleSelect API to take an array
of **objectMetadataName** instead of only one to deal with both our
usecases.

---------

Co-authored-by: Charles Bochet <charles@twenty.com>
2025-10-07 17:25:11 +02:00
Charles Bochet cd4800eb86 Fix exception handling on messaging + optimistic on object delete (#14894)
## What

In this PR, we are fixing two issues:
- while deleting an object, the views are not properly refreshed leading
to FE bug. This is due to the fact that we were refetching **views
associated to the object** which has been deleted. As views are properly
deleted in the BE, the FE gets no views from this call and cannot
optimistically react. In this case, I'm triggering a complete view
refetch
- messaging error handling had a hole
2025-10-04 20:19:26 +02:00