Commit Graph

791 Commits

Author SHA1 Message Date
Etienne 2b702b2b45 Navigation Menu Item - Migration in v1.18 (#17991)
Add navigation menu item data migration command in 1.18
2026-02-17 14:00:24 +01:00
Paul Rastoin 2b7b05de2e [OBJECT_MANIFEST_BREAKING_CHANGE] Sync returns workspace migration (#17918)
# Introduction
In this PR we start returning a workspace migration post sync so it can
committed and provided within the tarball

## Universal aggregators utils
Created two utils
### deleteUniversalFlatEntityForeignKeyAggregators
Used when building a universal create action, a newly created actions
should not contain any aggregated foreign key so they won't be codegen
in the workspace migration but also they are overriden at uninversal to
flat transpilation anw

### resetUniversalFlatEntityForeignKeyAggregators
Used before validating a new flat entity creation, some validator will
consume the fk aggregator in order to validate integrity, but of
optimstically provided it can result to errors. To avoid caller
responsability we override them here

## create-field-action refactor
Refactored the universal and flat field create action to be following
the base actions in order to ease typing
Also it was tailored to handle unlimited amount of flat field metadata
in the same actions whereas in the reality we were always only sending
at max 2 ( for relation fields )

Note: relation field has to be provided at the same as if not optimistic
would fail to retrieve circular universal identifiers

## ObjectManifest
Now always expect a `labelIdentifierFieldMetadataUniversalIdentifier`

## Integration test
Created an integration test that creates an app, sync a first manifest
and a second implying update workspace migration action generation
2026-02-16 15:00:24 +01:00
Félix Malfait c775d65952 feat: configure standard views and migrate attachment seeds to FILES field (#17958)
## 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
b1939bbf6f8cce294f9b4cdec06b19778daa205e. 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>
2026-02-16 13:34:56 +01:00
neo773 d54b713264 Respect Gmail retry-after in messaging throttle (#17850)
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().
2026-02-13 18:20:37 +01:00
Etienne 5c2c588885 Files - Migrate attachments in activities (#17808)
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
2026-02-13 09:11:23 +00:00
Weiko 3075707bf8 Prefil fields widgets to standard app (#17897)
## 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
2026-02-12 16:54:30 +00:00
Etienne eb52976e91 File v2 - Backfill mimeType and size - command (#17875) 2026-02-12 10:49:10 +00:00
martmull a4ed043d43 Logic function refactorization (#17861)
As title
2026-02-12 11:40:49 +01:00
Weiko 6aca1dd013 Introducing view field group syncable entity (#17867)
## 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>
2026-02-11 22:02:58 +01: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
Marie bc72879c70 Fix commands order for v1.17.0 (#17839)
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>
2026-02-10 18:47:59 +01:00
Charles Bochet 37b9a55382 Migrate metrics to prometheus (#17810)
<img width="1316" height="611" alt="image"
src="https://github.com/user-attachments/assets/277a63ed-2a8b-41ff-be78-281de8891579"
/>
2026-02-09 19:09:13 +01:00
Charles Bochet d7c28d6455 Fix file constraint migration (#17796)
## 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
2026-02-09 14:40:35 +01:00
Marie 1edce5088c Flush cache before and after upgrade for self-hosts (#17800)
as per title
2026-02-09 14:35:13 +01:00
Félix Malfait 3216b634a3 feat: improve AI chat - system prompt, tool output, context window display (#17769)
⚠️ **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>
2026-02-09 14:26:02 +01:00
martmull 9162685b2e Reorganize logic function files (#17766)
reorganize according to

<img width="1243" height="725" alt="Pasted Graphic"
src="https://github.com/user-attachments/assets/ba65dd10-8eec-4b13-ad49-9726edd3b79c"
/>

Not working yet
2026-02-09 12:36:39 +01:00
Félix Malfait ece265c6e4 Harden local file storage driver path resolution (#17783)
## 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>
2026-02-09 09:54:10 +00:00
Weiko 9aa63f7ddc Add sync front component (#17748)
## Context
Allow twenty apps to sync front components
2026-02-08 23:00:23 +00:00
Paul Rastoin 3dc5b162c7 Spread in parent and requires FlatEntity.__universal (#17753)
# 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 },
  });
```
2026-02-06 17:02:02 +00:00
Abdul Rahman f6beb06364 Migrate favorites to navigation menu items (1.17 upgrade) (#17477)
**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>
2026-02-06 13:42:20 +00:00
Félix Malfait d537144ab1 fix: add command to fix morph relation field name mismatches (#17757)
## 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;
```
2026-02-06 11:38:50 +01:00
Félix Malfait 27da9d60eb fix: set isActive=true in morph migration & add system fields toggle (#17736)
## 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.
2026-02-05 13:35:46 +00:00
Charles Bochet 67a98f77e3 Refactor workflow-logic-function-interaction (#17699)
# 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.
2026-02-05 00:46:55 +00:00
Félix Malfait 177cae8c53 feat: audit Logs (#17660) 2026-02-04 13:30:55 +01:00
Etienne f6b210dd0d Files v2 - File table migration + data migration command (#17661)
To add also in 1.16 patch
2026-02-04 10:28:55 +00:00
Etienne 50ef231704 Billing - Fix inactivity (#17703)
- Add suspendedAt column on workspace table
- Update suspendedAt field when workspace suspended or re-activated
- Compute inactivity on suspendedAt



Tested : 
- Cancel subscription
- Cancel subscription + Subscribe again
- Cleaning command
2026-02-04 10:17:27 +00:00
Charles Bochet 402d149ee1 Remove logic function layer (#17697)
## 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.
2026-02-04 10:17:27 +01:00
Abdullah. 7867617385 Migrate noteTarget and taskTarget to Morph. (#17476)
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.
2026-02-04 02:53:06 +00:00
Charles Bochet 867b03393b Backfill package json for custom and standard app (#17681)
# 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
2026-02-03 21:23:29 +01:00
Paul Rastoin 476bdf764c Refactor flat entity maps to be universal oriented (#17665)
# 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 )
2026-02-03 16:16:02 +00:00
Weiko 5c91a96e84 Add position to page layout widgets (#17643)
## Context
Introducing a new position column in PageLayoutWidget. This is because
we already have gridPosition but all widgets are not supposed to fit in
a grid (see pageLayoutTab layoutMode (GRID, VERTICAL_LIST, CANVAS...),
position will now support the different layoutMode

## Implementation
- Adding the new column (not renaming to avoid breaking changes in the
dashboards)
- Add proper validation for each layout mode
- Update standard page layout to not always use GRID but rely on the tab
layout mode

<details>

<summary>Graphql Query</summary>

```graphql
{
  "data": {
    "getPageLayouts": [
      {
        "tabs": [
          {
            "title": "Tab 1",
            "layoutMode": "GRID",
            "widgets": [
              {
                "title": "Untitled Rich Text",
                "position": {
                  "column": 0,
                  "columnSpan": 6,
                  "layoutMode": "GRID",
                  "row": 0,
                  "rowSpan": 6
                }
              },
              {
                "title": "Deals by Company",
                "position": {
                  "column": 6,
                  "columnSpan": 6,
                  "layoutMode": "GRID",
                  "row": 0,
                  "rowSpan": 6
                }
              },
              {
                "title": "Pipeline Value by Stage",
                "position": {
                  "column": 0,
                  "columnSpan": 6,
                  "layoutMode": "GRID",
                  "row": 6,
                  "rowSpan": 6
                }
              },
              {
                "title": "Revenue Timeline",
                "position": {
                  "column": 6,
                  "columnSpan": 6,
                  "layoutMode": "GRID",
                  "row": 6,
                  "rowSpan": 6
                }
              },
              {
                "title": "Opportunities by Owner",
                "position": {
                  "column": 0,
                  "columnSpan": 6,
                  "layoutMode": "GRID",
                  "row": 12,
                  "rowSpan": 6
                }
              },
              {
                "title": "Stock market (Iframe)",
                "position": {
                  "column": 6,
                  "columnSpan": 6,
                  "layoutMode": "GRID",
                  "row": 12,
                  "rowSpan": 8
                }
              },
              {
                "title": "Deals created this month",
                "position": {
                  "column": 0,
                  "columnSpan": 3,
                  "layoutMode": "GRID",
                  "row": 18,
                  "rowSpan": 2
                }
              },
              {
                "title": "Deal value created this month",
                "position": {
                  "column": 3,
                  "columnSpan": 3,
                  "layoutMode": "GRID",
                  "row": 18,
                  "rowSpan": 2
                }
              }
            ]
          }
        ]
      },
      {
        "tabs": [
          {
            "title": "Home",
            "layoutMode": "VERTICAL_LIST",
            "widgets": [
              {
                "title": "Fields",
                "position": {
                  "layoutMode": "VERTICAL_LIST",
                  "index": 0
                }
              }
            ]
          },
          {
            "title": "Timeline",
            "layoutMode": "CANVAS",
            "widgets": [
              {
                "title": "Timeline",
                "position": {
                  "layoutMode": "CANVAS"
                }
              }
            ]
          },
          {
            "title": "Tasks",
            "layoutMode": "CANVAS",
            "widgets": [
              {
                "title": "Tasks",
                "position": {
                  "layoutMode": "CANVAS"
                }
              }
            ]
          },
          {
            "title": "Notes",
            "layoutMode": "CANVAS",
            "widgets": [
              {
                "title": "Notes",
                "position": {
                  "layoutMode": "CANVAS"
                }
              }
            ]
          },
          {
            "title": "Files",
            "layoutMode": "CANVAS",
            "widgets": [
              {
                "title": "Files",
                "position": {
                  "layoutMode": "CANVAS"
                }
              }
            ]
          },
          {
            "title": "Emails",
            "layoutMode": "CANVAS",
            "widgets": [
              {
                "title": "Emails",
                "position": {
                  "layoutMode": "CANVAS"
                }
              }
            ]
          },
          {
            "title": "Calendar",
            "layoutMode": "CANVAS",
            "widgets": [
              {
                "title": "Calendar",
                "position": {
                  "layoutMode": "CANVAS"
                }
              }
            ]
          }
        ]
      },
      {
        "tabs": [
          {
            "title": "Home",
            "layoutMode": "VERTICAL_LIST",
            "widgets": [
              {
                "title": "Fields",
                "position": {
                  "layoutMode": "VERTICAL_LIST",
                  "index": 0
                }
              }
            ]
          },
          {
            "title": "Timeline",
            "layoutMode": "CANVAS",
            "widgets": [
              {
                "title": "Timeline",
                "position": {
                  "layoutMode": "CANVAS"
                }
              }
            ]
          },
          {
            "title": "Tasks",
            "layoutMode": "CANVAS",
            "widgets": [
              {
                "title": "Tasks",
                "position": {
                  "layoutMode": "CANVAS"
                }
              }
            ]
          },
          {
            "title": "Notes",
            "layoutMode": "CANVAS",
            "widgets": [
              {
                "title": "Notes",
                "position": {
                  "layoutMode": "CANVAS"
                }
              }
            ]
          },
          {
            "title": "Files",
            "layoutMode": "CANVAS",
            "widgets": [
              {
                "title": "Files",
                "position": {
                  "layoutMode": "CANVAS"
                }
              }
            ]
          },
          {
            "title": "Emails",
            "layoutMode": "CANVAS",
            "widgets": [
              {
                "title": "Emails",
                "position": {
                  "layoutMode": "CANVAS"
                }
              }
            ]
          },
          {
            "title": "Calendar",
            "layoutMode": "CANVAS",
            "widgets": [
              {
                "title": "Calendar",
                "position": {
                  "layoutMode": "CANVAS"
                }
              }
            ]
          }
        ]
      },
      {
        "tabs": [
          {
            "title": "Home",
            "layoutMode": "VERTICAL_LIST",
            "widgets": [
              {
                "title": "Fields",
                "position": {
                  "layoutMode": "VERTICAL_LIST",
                  "index": 0
                }
              }
            ]
          },
          {
            "title": "Timeline",
            "layoutMode": "CANVAS",
            "widgets": [
              {
                "title": "Timeline",
                "position": {
                  "layoutMode": "CANVAS"
                }
              }
            ]
          },
          {
            "title": "Tasks",
            "layoutMode": "CANVAS",
            "widgets": [
              {
                "title": "Tasks",
                "position": {
                  "layoutMode": "CANVAS"
                }
              }
            ]
          },
          {
            "title": "Notes",
            "layoutMode": "CANVAS",
            "widgets": [
              {
                "title": "Notes",
                "position": {
                  "layoutMode": "CANVAS"
                }
              }
            ]
          },
          {
            "title": "Files",
            "layoutMode": "CANVAS",
            "widgets": [
              {
                "title": "Files",
                "position": {
                  "layoutMode": "CANVAS"
                }
              }
            ]
          },
          {
            "title": "Emails",
            "layoutMode": "CANVAS",
            "widgets": [
              {
                "title": "Emails",
                "position": {
                  "layoutMode": "CANVAS"
                }
              }
            ]
          },
          {
            "title": "Calendar",
            "layoutMode": "CANVAS",
            "widgets": [
              {
                "title": "Calendar",
                "position": {
                  "layoutMode": "CANVAS"
                }
              }
            ]
          }
        ]
      },
      {
        "tabs": [
          {
            "title": "Home",
            "layoutMode": "VERTICAL_LIST",
            "widgets": [
              {
                "title": "Fields",
                "position": {
                  "layoutMode": "VERTICAL_LIST",
                  "index": 0
                }
              },
              {
                "title": "Note",
                "position": {
                  "layoutMode": "VERTICAL_LIST",
                  "index": 1
                }
              }
            ]
          },
          {
            "title": "Note",
            "layoutMode": "CANVAS",
            "widgets": [
              {
                "title": "Note",
                "position": {
                  "layoutMode": "CANVAS"
                }
              }
            ]
          },
          {
            "title": "Timeline",
            "layoutMode": "CANVAS",
            "widgets": [
              {
                "title": "Timeline",
                "position": {
                  "layoutMode": "CANVAS"
                }
              }
            ]
          },
          {
            "title": "Files",
            "layoutMode": "CANVAS",
            "widgets": [
              {
                "title": "Files",
                "position": {
                  "layoutMode": "CANVAS"
                }
              }
            ]
          }
        ]
      },
      {
        "tabs": [
          {
            "title": "Home",
            "layoutMode": "VERTICAL_LIST",
            "widgets": [
              {
                "title": "Fields",
                "position": {
                  "layoutMode": "VERTICAL_LIST",
                  "index": 0
                }
              },
              {
                "title": "Note",
                "position": {
                  "layoutMode": "VERTICAL_LIST",
                  "index": 1
                }
              }
            ]
          },
          {
            "title": "Note",
            "layoutMode": "CANVAS",
            "widgets": [
              {
                "title": "Note",
                "position": {
                  "layoutMode": "CANVAS"
                }
              }
            ]
          },
          {
            "title": "Timeline",
            "layoutMode": "CANVAS",
            "widgets": [
              {
                "title": "Timeline",
                "position": {
                  "layoutMode": "CANVAS"
                }
              }
            ]
          },
          {
            "title": "Files",
            "layoutMode": "CANVAS",
            "widgets": [
              {
                "title": "Files",
                "position": {
                  "layoutMode": "CANVAS"
                }
              }
            ]
          }
        ]
      },
      {
        "tabs": [
          {
            "title": "Flow",
            "layoutMode": "CANVAS",
            "widgets": [
              {
                "title": "Flow",
                "position": {
                  "layoutMode": "CANVAS"
                }
              }
            ]
          }
        ]
      },
      {
        "tabs": [
          {
            "title": "Home",
            "layoutMode": "VERTICAL_LIST",
            "widgets": [
              {
                "title": "Fields",
                "position": {
                  "layoutMode": "VERTICAL_LIST",
                  "index": 0
                }
              }
            ]
          },
          {
            "title": "Flow",
            "layoutMode": "CANVAS",
            "widgets": [
              {
                "title": "Flow",
                "position": {
                  "layoutMode": "CANVAS"
                }
              }
            ]
          }
        ]
      },
      {
        "tabs": [
          {
            "title": "Home",
            "layoutMode": "VERTICAL_LIST",
            "widgets": [
              {
                "title": "Fields",
                "position": {
                  "layoutMode": "VERTICAL_LIST",
                  "index": 0
                }
              }
            ]
          },
          {
            "title": "Flow",
            "layoutMode": "CANVAS",
            "widgets": [
              {
                "title": "Flow",
                "position": {
                  "layoutMode": "CANVAS"
                }
              }
            ]
          }
        ]
      }
    ]
  }
}
```

</details>
2026-02-03 15:54:01 +00:00
Charles Bochet 97867c11e6 Upgrade application model (#17673) 2026-02-03 16:44:00 +01:00
martmull 65678ed99f Upload files instead ofsources (#17608) 2026-02-03 12:11:10 +01:00
Paul Rastoin d35d5c0463 [BREAKING_CHANGE] Deprecate remaining entities standardId (#17639)
# 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 )
2026-02-03 09:06:24 +01:00
Charles Bochet cfdcc0065c Migrate workflow serverless to logic (#17646)
Migrations commands

---------

Co-authored-by: Charles Bochet <charles@macbook-pro-de-charles-1.home>
2026-02-02 19:39:20 +01:00
Paul Rastoin 75921e79bf [FIXES_MAIN] Remove objectMetadata standardId (#17632)
# 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`
2026-02-02 14:26:27 +00:00
Paul Rastoin bd9688421f ObjectMetadata and FieldMetadata agnostic workspace migration runner (#17572)
# 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`
2026-02-02 12:22:38 +00:00
Charles Bochet 4a770eafa1 Rework logic function module (#17588)
core-modules/logic-function/
├── logic-function.module.ts
├── logic-function-executor/
│   ├── logic-function-executor.module.ts
│   ├── commands/
│   │   └── add-packages.command.ts
│   ├── constants/
│   │   └── logic-function-executor.constants.ts
│   ├── factories/
│   │   └── logic-function-module.factory.ts
│   ├── interfaces/
│   │   └── logic-function-executor.interface.ts
│   └── services/
│       └── logic-function-executor.service.ts
├── logic-function-build/
│   ├── logic-function-build.module.ts
│   ├── services/
│   │   └── logic-function-build.service.ts
│   └── utils/
│       └── get-logic-function-base-folder-path.util.ts
├── logic-function-drivers/
│   ├── logic-function-drivers.module.ts
│   ├── constants/
│   │   └── ...
│   ├── drivers/
│   │   ├── disabled.driver.ts
│   │   ├── lambda.driver.ts
│   │   └── local.driver.ts
│   ├── interfaces/
│   │   └── logic-function-executor-driver.interface.ts
│   ├── layers/
│   │   └── ...
│   └── utils/
│       └── ...
├── logic-function-layer/
│   ├── logic-function-layer.module.ts
│   └── services/
│       └── logic-function-layer.service.ts
└── logic-function-trigger/
    ├── logic-function-trigger.module.ts
    ├── jobs/
    │   └── logic-function-trigger.job.ts
    └── triggers/
        ├── cron/
        ├── database-event/
        └── route/
            ├── exceptions/
            ├── services/
            │   └── route-trigger.service.ts
            └── utils/
2026-01-30 19:28:20 +01:00
Marie 0001c2e7d0 [Apps] Apps marketplace (first draft) (#17562)
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
2026-01-30 17:32:32 +00:00
Abdul Rahman 51a2a90b0a feat: CommandMenuItem entity and FrontComponent support in command menu (#17555)
https://github.com/user-attachments/assets/6f172ffc-d43d-42fd-a26b-94f591fb767e
2026-01-30 16:35:06 +00:00
neo773 cc1ca630fd Workflow Send Email Node Multiple Recipients Support (#17458)
This PR adds support sending emails to multiple recipients

Figma Reference:

https://www.figma.com/design/xt8O9mFeLl46C5InWwoMrN/Twenty?node-id=88868-88963&t=Ya0csmNlN4xxczvV-11

Demo:


https://github.com/user-attachments/assets/ecaeaaec-fe42-4fb5-96d3-a91d08b30148
2026-01-30 16:54:21 +01:00
martmull f46da3eefd Update manifest structure (#17547)
Move all sync entities in an `entities` key. Rename functions to
logicFunctions

```json
{
  application: {
    ...
  },
  entities: {
    objects: [],
    logicFunctions: [],
    ...
  }
}
```
2026-01-30 16:26:45 +01:00
Charles Bochet 5996d0fc03 Refactor workflow to use new functions (#17552)
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
2026-01-30 15:42:36 +01:00
Etienne 9439afde22 Files - Delete command (#17576) 2026-01-30 13:38:30 +00:00
Charles Bochet 6eebf6f23a Remove versions from logicFunction (#17540)
## 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
2026-01-30 14:30:49 +01:00
Weiko 446afcbc30 Add standard record page layout (#17567) 2026-01-30 11:40:36 +00:00
Weiko 7d69000ab7 Update pageLayout* data models for backend recordPageLayout refactor (#17446)
<img width="814" height="356" alt="Screenshot 2026-01-26 at 16 09 27"
src="https://github.com/user-attachments/assets/91d95a1d-cc33-4bf0-a63e-4d1815030983"
/>
2026-01-29 13:39:46 +00:00
Etienne 09de762285 Files v2 - FILES field update/create in common API (#17400)
## FILES Field update interface

```
mutation {
  updateCompany(
    id: "company-uuid"
    data: {
      filesfield: [
          { fileId: "new-file-uuid", label: "new-document.pdf" }
        ]
    }
  ) {
    id
    filesfield {
      fileId
      label
      extension
    }
  }
}
```
2026-01-29 13:33:26 +00:00
Paul Rastoin 97a37604bd Fix UpdateTaskOnDeleteActionCommand order and simplify (#17527)
Already introduced in 1.16.5 for self host
2026-01-29 10:55:04 +00:00