Move open-record-in to object metadata and member preference (#23614)

Replaces the per-view "Open in" setting with a two-level model,
following up on #23422 / #23424 and superseding the closed #23446 and
#23457:

- `objectMetadata.openRecordIn`: `SIDE_PANEL` | `RECORD_PAGE` |
`USER_CHOICE` (default `USER_CHOICE`)
- `workspaceMember.openRecordIn`: `SIDE_PANEL` | `RECORD_PAGE` (default
`SIDE_PANEL`), editable in Settings > Experience

The rule: records open where the member prefers, unless the object pins
them, and never in a panel there is no room for (mobile always resolves
to the record page).

## Why

Having the setting on views, objects and members at once was heavy, and
view-level resolution was fragile: a chip rendered outside a view
(notes, front components, kanban cards pointing at another object) had
no view to read from, which is the class of bug behind #23422.
Resolution is now context-free: it needs only the object, the current
member and the viewport, so chips behave identically everywhere by
construction.

## Changes

**Object level**
- New `openRecordIn` enum column on `objectMetadata`, editable through
`updateOneObject` and surfaced in Settings > Data model > Object >
Layout ("Open records in": Member preference / Side Panel / Record Page)
- Standard definitions pin `workflow`, `workflowVersion`, `dashboard`
and `messageCampaign` to the record page (matching the previously
hardcoded list) and `calendarEvent` to the side panel (it has no curated
record page); everything else, including `workflowRun`, follows the
member preference
- Apps can set it in `defineObject()` via the object manifest

**Member level**
- New `openRecordIn` standard field on `workspaceMember`, persisted
through the existing settings path (same as `colorScheme`) and exposed
in Settings > Experience

**View level (deprecated)**
- `view.openRecordIn` is no longer read or written by the frontend; the
"Open in" entry is gone from the view options dropdown
- The column, DTO field and inputs are kept for one release for API
compatibility: the output field carries a `deprecationReason`, the
inputs keep accepting the value with a `Deprecated:` description (NestJS
silently drops input fields that have a `deprecationReason`, which would
have been a breaking change)

**Upgrade (2.27)**
- Fast instance command adds the `objectMetadata.openRecordIn` column
defaulting to `USER_CHOICE`
- Workspace command adds the `workspaceMember.openRecordIn` field
- Workspace command seeds the object column from the standard
definitions (any non-`USER_CHOICE` value), then lifts deliberate
per-view record page choices onto objects the definitions don't pin

**Debt removed**
- `canOpenObjectInSidePanel` hardcoded object list and its test
- `ObjectOptionsDropdownLayoutOpenInContent` and the `layoutOpenIn`
dropdown wiring
- `DefaultViewOpenRecordIn`
- Context-store/view-based resolution in `useResolveOpenRecordIn` (now
reads object metadata + member + viewport)
- Front components no longer guess from the current view: an explicit
side-panel call honours a pinned object and the viewport, nothing else

## Verification

- Ran the three upgrade commands against a live database: column
created, the pinned standard objects seeded per workspace (record page
pins plus calendarEvent to side panel), member field backfilled to
`SIDE_PANEL`; seed rerun is a no-op
- Seed command verified on a simulated pre-upgrade workspace (index view
set to record page on company): pins the standard objects plus company,
idempotent on rerun
- Both packages typecheck and lint clean; affected unit suites and the
application sync, view creation and metadata cache integration specs
pass

---------

Co-authored-by: Thomas des Francs <tdesfrancs@gmail.com>
This commit is contained in:
Félix Malfait
2026-07-31 18:28:55 +02:00
committed by GitHub
parent 6f361a9bf2
commit f663cd3c68
116 changed files with 2395 additions and 1544 deletions
@@ -1,4 +1,4 @@
import { FieldMetadataType } from 'twenty-shared/types';
import { FieldMetadataType, ObjectOpenRecordIn } from 'twenty-shared/types';
import { type WorkspaceEntityDuplicateCriteria } from 'src/engine/api/graphql/workspace-query-builder/types/workspace-entity-duplicate-criteria.type';
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
@@ -166,6 +166,7 @@ export const mockPersonFlatObjectMetadata = (
overrides: null,
isUIEditable: true,
isUICreatable: true,
openRecordIn: ObjectOpenRecordIn.USER_CHOICE,
applicationUniversalIdentifier: 'test-application-id',
fieldUniversalIdentifiers: mockFieldMetadatas.map(
(field) => field.universalIdentifier,
@@ -1,4 +1,5 @@
import { type ObjectManifest } from 'twenty-shared/application';
import { ObjectOpenRecordIn } from 'twenty-shared/types';
import { type UniversalFlatObjectMetadata } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-object-metadata.type';
@@ -19,6 +20,7 @@ export const fromObjectManifestToUniversalFlatObjectMetadata = ({
labelSingular: objectManifest.labelSingular,
labelPlural: objectManifest.labelPlural,
color: null,
openRecordIn: objectManifest.openRecordIn ?? ObjectOpenRecordIn.USER_CHOICE,
description: objectManifest.description ?? null,
icon: objectManifest.icon ?? null,
overrides: null,
@@ -1,4 +1,8 @@
import { FieldMetadataType, RelationType } from 'twenty-shared/types';
import {
FieldMetadataType,
ObjectOpenRecordIn,
RelationType,
} from 'twenty-shared/types';
import {
computeUpdatedFieldsFromDiff,
@@ -39,6 +43,7 @@ const mockObjectMetadata: FlatObjectMetadata = {
overrides: null,
isUIEditable: true,
isUICreatable: true,
openRecordIn: ObjectOpenRecordIn.USER_CHOICE,
labelIdentifierFieldMetadataId: null,
imageIdentifierFieldMetadataId: null,
duplicateCriteria: null,
@@ -1,3 +1,4 @@
import { OpenRecordIn } from 'twenty-shared/types';
import { Test, type TestingModule } from '@nestjs/testing';
import { getRepositoryToken } from '@nestjs/typeorm';
@@ -302,6 +303,7 @@ describe('UserWorkspaceService', () => {
lastName: user.lastName,
},
colorScheme: 'System',
openRecordIn: OpenRecordIn.SIDE_PANEL,
userId: user.id,
userEmail: user.email,
locale: 'en',
@@ -2,7 +2,7 @@ import { Logger } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { type APP_LOCALES, SOURCE_LOCALE } from 'twenty-shared/translations';
import { FileFolder } from 'twenty-shared/types';
import { FileFolder, OpenRecordIn } from 'twenty-shared/types';
import { assertIsDefinedOrThrow, isDefined } from 'twenty-shared/utils';
import { IsNull, Not, type QueryRunner, type Repository } from 'typeorm';
@@ -188,6 +188,7 @@ export class UserWorkspaceService {
lastName: user.lastName,
},
colorScheme: 'System',
openRecordIn: OpenRecordIn.SIDE_PANEL,
userId: user.id,
userEmail: user.email,
avatarUrl: userWorkspace.defaultAvatarUrl ?? null,
@@ -1,4 +1,6 @@
import { Field, Int, ObjectType } from '@nestjs/graphql';
import { Field, Int, ObjectType, registerEnumType } from '@nestjs/graphql';
import { OpenRecordIn } from 'twenty-shared/types';
import { Max, Min } from 'class-validator';
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
@@ -9,6 +11,8 @@ import {
WorkspaceMemberTimeFormatEnum,
} from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
registerEnumType(OpenRecordIn, { name: 'OpenRecordIn' });
@ObjectType('FullName')
export class FullNameDTO {
@Field({ nullable: false })
@@ -32,6 +36,9 @@ export class WorkspaceMemberDTO {
@Field({ nullable: false })
colorScheme: string;
@Field(() => OpenRecordIn, { nullable: false })
openRecordIn: OpenRecordIn;
@Field({ nullable: true })
avatarUrl: string;
@@ -16,7 +16,7 @@ import {
type WorkspaceMemberTimeFormatEnum,
type WorkspaceMemberWorkspaceEntity,
} from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
import { FileFolder } from 'twenty-shared/types';
import { FileFolder, type OpenRecordIn } from 'twenty-shared/types';
export type ToWorkspaceMemberDtoArgs = {
workspaceMemberEntity: WorkspaceMemberWorkspaceEntity;
@@ -69,6 +69,7 @@ export class WorkspaceMemberTranspiler {
name,
userEmail,
colorScheme,
openRecordIn,
locale,
timeFormat,
timeZone,
@@ -98,6 +99,7 @@ export class WorkspaceMemberTranspiler {
avatarUrl,
userWorkspaceId: userWorkspace.id,
colorScheme,
openRecordIn: openRecordIn as OpenRecordIn,
dateFormat: dateFormat as WorkspaceMemberDateFormatEnum,
locale,
timeFormat: timeFormat as WorkspaceMemberTimeFormatEnum,
@@ -166,6 +166,7 @@ exports[`ALL_UNIVERSAL_FLAT_ENTITY_PROPERTIES_TO_COMPARE_AND_STRINGIFY should ma
},
"objectMetadata": {
"propertiesToCompare": [
"openRecordIn",
"color",
"description",
"icon",
@@ -28,6 +28,7 @@ exports[`registry-derived override property maps derives the overridable propert
"logicFunction": [],
"navigationMenuItem": [],
"objectMetadata": [
"openRecordIn",
"color",
"description",
"icon",
@@ -170,6 +170,12 @@ export const ALL_ENTITY_PROPERTIES_CONFIGURATION_BY_METADATA_NAME = {
toStringify: false,
universalProperty: undefined,
},
openRecordIn: {
toCompare: true,
toStringify: false,
universalProperty: undefined,
isOverridable: true,
},
color: {
toCompare: true,
toStringify: false,
@@ -41,6 +41,7 @@ type Assertions = [
keyof FlatEntityUpdate<'objectMetadata'>,
| 'icon'
| 'color'
| 'openRecordIn'
| 'description'
| 'isActive'
| 'overrides'
@@ -1,3 +1,4 @@
import { ObjectOpenRecordIn } from 'twenty-shared/types';
import { faker } from '@faker-js/faker';
import { TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER } from 'twenty-shared/application';
@@ -37,6 +38,7 @@ export const getFlatObjectMetadataMock = (
isSystem: false,
isUIEditable: true,
isUICreatable: true,
openRecordIn: ObjectOpenRecordIn.USER_CHOICE,
labelIdentifierFieldMetadataId,
labelPlural: 'default flat object metadata label plural',
labelSingular: 'default flat object metadata label singular',
@@ -3,6 +3,7 @@ import { type MetadataEntityPropertyName } from 'src/engine/metadata-modules/fla
export const FLAT_OBJECT_METADATA_EDITABLE_PROPERTIES = {
custom: [
'color',
'openRecordIn',
'description',
'icon',
'isActive',
@@ -17,6 +18,7 @@ export const FLAT_OBJECT_METADATA_EDITABLE_PROPERTIES = {
],
standard: [
'color',
'openRecordIn',
'description',
'icon',
'isActive',
@@ -1,4 +1,5 @@
import { getFieldUniversalIdentifier } from 'twenty-shared/application';
import { ObjectOpenRecordIn } from 'twenty-shared/types';
import {
capitalize,
isDefined,
@@ -60,6 +61,7 @@ export const fromCreateObjectInputToFlatObjectMetadataAndFlatFieldMetadatasToCre
updatedAt: createdAt,
duplicateCriteria: null,
color: createObjectInput.color ?? null,
openRecordIn: ObjectOpenRecordIn.USER_CHOICE,
description: createObjectInput.description ?? null,
icon: createObjectInput.icon ?? null,
isActive: true,
@@ -19,6 +19,7 @@ export const fromFlatObjectMetadataToObjectMetadataDto = (
isLabelSyncedWithName,
isRemote,
isSearchable,
openRecordIn,
isSystem,
isUIEditable,
isUICreatable,
@@ -39,6 +40,7 @@ export const fromFlatObjectMetadataToObjectMetadataDto = (
isLabelSyncedWithName,
isRemote,
isSearchable,
openRecordIn,
isSystem,
isUIEditable,
isUICreatable,
@@ -1,4 +1,11 @@
import { Field, HideField, ObjectType } from '@nestjs/graphql';
import {
Field,
HideField,
ObjectType,
registerEnumType,
} from '@nestjs/graphql';
import { ObjectOpenRecordIn } from 'twenty-shared/types';
import {
Authorize,
@@ -14,6 +21,8 @@ import { FieldMetadataDTO } from 'src/engine/metadata-modules/field-metadata/dto
import { IndexMetadataDTO } from 'src/engine/metadata-modules/index-metadata/dtos/index-metadata.dto';
import { type ObjectMetadataOverrides } from 'src/engine/metadata-modules/object-metadata/types/object-metadata-overrides.type';
registerEnumType(ObjectOpenRecordIn, { name: 'ObjectOpenRecordIn' });
@ObjectType('Object')
@Authorize({
// oxlint-disable-next-line typescript/no-explicit-any
@@ -87,6 +96,9 @@ export class ObjectMetadataDTO {
@FilterableField()
isSearchable: boolean;
@Field(() => ObjectOpenRecordIn)
openRecordIn: ObjectOpenRecordIn;
@HideField()
workspaceId: string;
@@ -1,8 +1,10 @@
import { Field, InputType } from '@nestjs/graphql';
import { Type } from 'class-transformer';
import { ObjectOpenRecordIn } from 'twenty-shared/types';
import {
IsBoolean,
IsEnum,
IsNotEmpty,
IsOptional,
IsString,
@@ -81,6 +83,11 @@ export class UpdateObjectPayload {
@IsOptional()
@Field({ nullable: true })
isSearchable?: boolean;
@IsEnum(ObjectOpenRecordIn)
@IsOptional()
@Field(() => ObjectOpenRecordIn, { nullable: true })
openRecordIn?: ObjectOpenRecordIn;
}
@InputType()
@@ -9,7 +9,10 @@ import {
UpdateDateColumn,
} from 'typeorm';
import { ObjectOpenRecordIn } from 'twenty-shared/types';
import { ADD_METADATA_OVERRIDES_COLUMN_UPGRADE_COMMAND_NAME } from 'src/database/commands/upgrade-version-command/2-19/add-metadata-overrides-column-upgrade-command-name.constant';
import { ADD_OBJECT_METADATA_OPEN_RECORD_IN_UPGRADE_COMMAND_NAME } from 'src/database/commands/upgrade-version-command/2-27/add-object-metadata-open-record-in-upgrade-command-name.constant';
import { DROP_METADATA_STANDARD_OVERRIDES_COLUMN_UPGRADE_COMMAND_NAME } from 'src/database/commands/upgrade-version-command/2-20/drop-metadata-standard-overrides-column-upgrade-command-name.constant';
import { type WorkspaceEntityDuplicateCriteria } from 'src/engine/api/graphql/workspace-query-builder/types/workspace-entity-duplicate-criteria.type';
import { WasIntroducedInUpgrade } from 'src/engine/core-modules/upgrade/decorators/was-introduced-in-upgrade.decorator';
@@ -66,6 +69,16 @@ export class ObjectMetadataEntity
@Column({ nullable: true, type: 'text' })
color: string | null;
@WasIntroducedInUpgrade({
upgradeCommandName: ADD_OBJECT_METADATA_OPEN_RECORD_IN_UPGRADE_COMMAND_NAME,
})
@Column({
type: 'enum',
enum: Object.values(ObjectOpenRecordIn),
default: ObjectOpenRecordIn.USER_CHOICE,
})
openRecordIn: ObjectOpenRecordIn;
@WasIntroducedInUpgrade({
upgradeCommandName: ADD_METADATA_OVERRIDES_COLUMN_UPGRADE_COMMAND_NAME,
})
@@ -23,6 +23,7 @@ export const fromObjectMetadataEntityToObjectMetadataDto = (
isUICreatable: entity.isUICreatable,
isUIReadOnly: !entity.isUIEditable,
isSearchable: entity.isSearchable,
openRecordIn: entity.openRecordIn,
isLabelSyncedWithName: entity.isLabelSyncedWithName,
workspaceId: entity.workspaceId,
labelIdentifierFieldMetadataId:
@@ -0,0 +1,2 @@
export const VIEW_OPEN_RECORD_IN_DEPRECATION =
'Superseded by objectMetadata.openRecordIn and the workspace member preference; kept one release for API compatibility, no longer read by the frontend.';
@@ -25,6 +25,7 @@ import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/
import { IsValidMetadataName } from 'src/engine/decorators/metadata/is-valid-metadata-name.decorator';
import { KANBAN_COLUMN_MAX_WIDTH } from 'src/engine/metadata-modules/view/constants/kanban-column-max-width.constant';
import { KANBAN_COLUMN_MIN_WIDTH } from 'src/engine/metadata-modules/view/constants/kanban-column-min-width.constant';
import { VIEW_OPEN_RECORD_IN_DEPRECATION } from 'src/engine/metadata-modules/view/constants/view-open-record-in-deprecation.constant';
@InputType()
export class CreateViewInput {
@@ -82,6 +83,7 @@ export class CreateViewInput {
@IsEnum(ViewOpenRecordIn)
@Field(() => ViewOpenRecordIn, {
nullable: true,
description: `Deprecated: ${VIEW_OPEN_RECORD_IN_DEPRECATION}`,
defaultValue: ViewOpenRecordIn.SIDE_PANEL,
})
openRecordIn?: ViewOpenRecordIn;
@@ -23,6 +23,7 @@ import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/
import { IsValidMetadataName } from 'src/engine/decorators/metadata/is-valid-metadata-name.decorator';
import { KANBAN_COLUMN_MAX_WIDTH } from 'src/engine/metadata-modules/view/constants/kanban-column-max-width.constant';
import { KANBAN_COLUMN_MIN_WIDTH } from 'src/engine/metadata-modules/view/constants/kanban-column-min-width.constant';
import { VIEW_OPEN_RECORD_IN_DEPRECATION } from 'src/engine/metadata-modules/view/constants/view-open-record-in-deprecation.constant';
// TODO: this should be refactored like for view-field.input.ts
// This is a temporary fix as we were extending the CreateViewInput class which was adding default values for the non filled fields
@@ -62,6 +63,7 @@ export class UpdateViewInput {
@IsEnum(ViewOpenRecordIn)
@Field(() => ViewOpenRecordIn, {
nullable: true,
description: `Deprecated: ${VIEW_OPEN_RECORD_IN_DEPRECATION}`,
})
openRecordIn?: ViewOpenRecordIn;
@@ -19,6 +19,7 @@ import {
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
import { KANBAN_COLUMN_MAX_WIDTH } from 'src/engine/metadata-modules/view/constants/kanban-column-max-width.constant';
import { KANBAN_COLUMN_MIN_WIDTH } from 'src/engine/metadata-modules/view/constants/kanban-column-min-width.constant';
import { VIEW_OPEN_RECORD_IN_DEPRECATION } from 'src/engine/metadata-modules/view/constants/view-open-record-in-deprecation.constant';
@InputType()
export class UpsertViewWidgetViewSettingsInput {
@@ -43,7 +44,10 @@ export class UpsertViewWidgetViewSettingsInput {
@IsOptional()
@IsEnum(ViewOpenRecordIn)
@Field(() => ViewOpenRecordIn, { nullable: true })
@Field(() => ViewOpenRecordIn, {
nullable: true,
description: `Deprecated: ${VIEW_OPEN_RECORD_IN_DEPRECATION}`,
})
openRecordIn?: ViewOpenRecordIn;
@IsOptional()
@@ -22,6 +22,7 @@ import { ViewFilterGroupDTO } from 'src/engine/metadata-modules/view-filter-grou
import { ViewFilterDTO } from 'src/engine/metadata-modules/view-filter/dtos/view-filter.dto';
import { ViewGroupDTO } from 'src/engine/metadata-modules/view-group/dtos/view-group.dto';
import { ViewSortDTO } from 'src/engine/metadata-modules/view-sort/dtos/view-sort.dto';
import { VIEW_OPEN_RECORD_IN_DEPRECATION } from 'src/engine/metadata-modules/view/constants/view-open-record-in-deprecation.constant';
registerEnumType(ViewOpenRecordIn, { name: 'ViewOpenRecordIn' });
registerEnumType(ViewType, { name: 'ViewType' });
@@ -61,6 +62,7 @@ export class ViewDTO {
@Field(() => ViewOpenRecordIn, {
nullable: false,
defaultValue: ViewOpenRecordIn.SIDE_PANEL,
deprecationReason: VIEW_OPEN_RECORD_IN_DEPRECATION,
})
openRecordIn: ViewOpenRecordIn;
@@ -121,6 +121,7 @@ export class ViewEntity
@Column({ nullable: false, default: false, type: 'boolean' })
isCustom: boolean;
// Deprecated: superseded by objectMetadata.openRecordIn and the member preference.
@Column({
type: 'enum',
enum: Object.values(ViewOpenRecordIn),
@@ -1,6 +1,7 @@
import {
type FieldMetadataType,
type ObjectsPermissions,
ObjectOpenRecordIn,
} from 'twenty-shared/types';
import { EntityManager } from 'typeorm';
import { EntityPersistExecutor } from 'typeorm/persistence/EntityPersistExecutor';
@@ -124,6 +125,7 @@ describe('WorkspaceEntityManager', () => {
isLabelSyncedWithName: false,
isUIEditable: true,
isUICreatable: true,
openRecordIn: ObjectOpenRecordIn.USER_CHOICE,
duplicateCriteria: null,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
@@ -1,4 +1,4 @@
import { FieldMetadataType } from 'twenty-shared/types';
import { FieldMetadataType, ObjectOpenRecordIn } from 'twenty-shared/types';
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
@@ -39,6 +39,7 @@ describe('getColumnNameToFieldMetadataIdMap', () => {
overrides: null,
isUIEditable: true,
isUICreatable: true,
openRecordIn: ObjectOpenRecordIn.USER_CHOICE,
labelIdentifierFieldMetadataId: null,
imageIdentifierFieldMetadataId: null,
duplicateCriteria: null,
@@ -1,4 +1,4 @@
import { FieldMetadataType } from 'twenty-shared/types';
import { FieldMetadataType, ObjectOpenRecordIn } from 'twenty-shared/types';
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
@@ -39,6 +39,7 @@ describe('getFieldMetadataIdToColumnNamesMap', () => {
overrides: null,
isUIEditable: true,
isUICreatable: true,
openRecordIn: ObjectOpenRecordIn.USER_CHOICE,
labelIdentifierFieldMetadataId: null,
imageIdentifierFieldMetadataId: null,
duplicateCriteria: null,
@@ -1,4 +1,8 @@
import { FieldMetadataType, type ObjectRecord } from 'twenty-shared/types';
import {
FieldMetadataType,
ObjectOpenRecordIn,
type ObjectRecord,
} from 'twenty-shared/types';
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
@@ -39,6 +43,7 @@ describe('isRecordMatchingRLSRowLevelPermissionPredicate', () => {
overrides: null,
isUIEditable: true,
isUICreatable: true,
openRecordIn: ObjectOpenRecordIn.USER_CHOICE,
labelIdentifierFieldMetadataId: null,
imageIdentifierFieldMetadataId: null,
duplicateCriteria: null,
@@ -2956,22 +2956,22 @@ exports[`getStandardObjectMetadataRelatedEntityIds should return standard object
"workspaceMember": {
"fields": {
"accountOwnerForCompanies": {
"id": "00000000-0000-0000-0000-000000000898",
"id": "00000000-0000-0000-0000-000000000899",
},
"assignedTasks": {
"id": "00000000-0000-0000-0000-000000000896",
"id": "00000000-0000-0000-0000-000000000897",
},
"avatarUrl": {
"id": "00000000-0000-0000-0000-000000000892",
"id": "00000000-0000-0000-0000-000000000893",
},
"blocklist": {
"id": "00000000-0000-0000-0000-000000000900",
},
"calendarEventParticipants": {
"id": "00000000-0000-0000-0000-000000000901",
},
"calendarEventParticipants": {
"id": "00000000-0000-0000-0000-000000000902",
},
"calendarStartDay": {
"id": "00000000-0000-0000-0000-000000000906",
"id": "00000000-0000-0000-0000-000000000907",
},
"colorScheme": {
"id": "00000000-0000-0000-0000-000000000890",
@@ -2983,7 +2983,7 @@ exports[`getStandardObjectMetadataRelatedEntityIds should return standard object
"id": "00000000-0000-0000-0000-000000000885",
},
"dateFormat": {
"id": "00000000-0000-0000-0000-000000000904",
"id": "00000000-0000-0000-0000-000000000905",
},
"deletedAt": {
"id": "00000000-0000-0000-0000-000000000884",
@@ -2992,22 +2992,25 @@ exports[`getStandardObjectMetadataRelatedEntityIds should return standard object
"id": "00000000-0000-0000-0000-000000000881",
},
"jobTitle": {
"id": "00000000-0000-0000-0000-000000000894",
"id": "00000000-0000-0000-0000-000000000895",
},
"locale": {
"id": "00000000-0000-0000-0000-000000000891",
"id": "00000000-0000-0000-0000-000000000892",
},
"messageParticipants": {
"id": "00000000-0000-0000-0000-000000000899",
"id": "00000000-0000-0000-0000-000000000900",
},
"name": {
"id": "00000000-0000-0000-0000-000000000889",
},
"numberFormat": {
"id": "00000000-0000-0000-0000-000000000907",
"id": "00000000-0000-0000-0000-000000000908",
},
"openRecordIn": {
"id": "00000000-0000-0000-0000-000000000891",
},
"ownedOpportunities": {
"id": "00000000-0000-0000-0000-000000000897",
"id": "00000000-0000-0000-0000-000000000898",
},
"position": {
"id": "00000000-0000-0000-0000-000000000887",
@@ -3016,13 +3019,13 @@ exports[`getStandardObjectMetadataRelatedEntityIds should return standard object
"id": "00000000-0000-0000-0000-000000000888",
},
"timeFormat": {
"id": "00000000-0000-0000-0000-000000000905",
"id": "00000000-0000-0000-0000-000000000906",
},
"timeZone": {
"id": "00000000-0000-0000-0000-000000000903",
"id": "00000000-0000-0000-0000-000000000904",
},
"timelineActivities": {
"id": "00000000-0000-0000-0000-000000000902",
"id": "00000000-0000-0000-0000-000000000903",
},
"updatedAt": {
"id": "00000000-0000-0000-0000-000000000883",
@@ -3031,29 +3034,29 @@ exports[`getStandardObjectMetadataRelatedEntityIds should return standard object
"id": "00000000-0000-0000-0000-000000000886",
},
"userEmail": {
"id": "00000000-0000-0000-0000-000000000893",
"id": "00000000-0000-0000-0000-000000000894",
},
"userId": {
"id": "00000000-0000-0000-0000-000000000895",
"id": "00000000-0000-0000-0000-000000000896",
},
},
"id": "00000000-0000-0000-0000-000000000913",
"id": "00000000-0000-0000-0000-000000000914",
"views": {
"allWorkspaceMembers": {
"id": "00000000-0000-0000-0000-000000000912",
"id": "00000000-0000-0000-0000-000000000913",
"viewFieldGroups": {},
"viewFields": {
"assignedTasks": {
"id": "00000000-0000-0000-0000-000000000911",
"id": "00000000-0000-0000-0000-000000000912",
},
"createdAt": {
"id": "00000000-0000-0000-0000-000000000909",
"id": "00000000-0000-0000-0000-000000000910",
},
"name": {
"id": "00000000-0000-0000-0000-000000000908",
"id": "00000000-0000-0000-0000-000000000909",
},
"ownedOpportunities": {
"id": "00000000-0000-0000-0000-000000000910",
"id": "00000000-0000-0000-0000-000000000911",
},
},
"viewGroups": {},
@@ -5,6 +5,7 @@ import {
FieldMetadataType,
NumberDataType,
RelationType,
OpenRecordIn,
} from 'twenty-shared/types';
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
@@ -163,6 +164,27 @@ export const buildWorkspaceMemberStandardFlatFieldMetadatas = ({
twentyStandardApplicationId,
now,
}),
openRecordIn: createStandardFieldFlatMetadata({
objectName,
workspaceId,
context: {
fieldName: 'openRecordIn',
type: FieldMetadataType.TEXT,
label: i18nLabel(msg`Open Records In`),
description: i18nLabel(
msg`Where records open for objects that follow the member's preference`,
),
icon: 'IconLayoutSidebarRight',
isSystem: true,
isNullable: false,
isUIEditable: false,
defaultValue: `'${OpenRecordIn.SIDE_PANEL}'`,
},
standardObjectMetadataRelatedEntityIds,
dependencyFlatEntityMaps,
twentyStandardApplicationId,
now,
}),
locale: createStandardFieldFlatMetadata({
objectName,
workspaceId,
@@ -1,5 +1,6 @@
import { msg } from '@lingui/core/macro';
import { STANDARD_OBJECTS } from 'twenty-shared/metadata';
import { ObjectOpenRecordIn } from 'twenty-shared/types';
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
import { type AllStandardObjectName } from 'src/engine/workspace-manager/twenty-standard-application/types/all-standard-object-name.type';
@@ -144,6 +145,7 @@ export const STANDARD_FLAT_OBJECT_METADATA_BUILDERS_BY_OBJECT_NAME = {
context: {
universalIdentifier: STANDARD_OBJECTS.calendarEvent.universalIdentifier,
nameSingular: 'calendarEvent',
openRecordIn: ObjectOpenRecordIn.SIDE_PANEL,
namePlural: 'calendarEvents',
labelSingular: i18nLabel(msg`Calendar event`),
labelPlural: i18nLabel(msg`Calendar events`),
@@ -232,6 +234,7 @@ export const STANDARD_FLAT_OBJECT_METADATA_BUILDERS_BY_OBJECT_NAME = {
context: {
universalIdentifier: STANDARD_OBJECTS.dashboard.universalIdentifier,
nameSingular: 'dashboard',
openRecordIn: ObjectOpenRecordIn.RECORD_PAGE,
namePlural: 'dashboards',
labelSingular: i18nLabel(msg`Dashboard`),
labelPlural: i18nLabel(msg`Dashboards`),
@@ -263,6 +266,7 @@ export const STANDARD_FLAT_OBJECT_METADATA_BUILDERS_BY_OBJECT_NAME = {
universalIdentifier:
STANDARD_OBJECTS.messageCampaign.universalIdentifier,
nameSingular: 'messageCampaign',
openRecordIn: ObjectOpenRecordIn.RECORD_PAGE,
namePlural: 'messageCampaigns',
labelSingular: i18nLabel(msg`Campaign`),
labelPlural: i18nLabel(msg`Campaigns`),
@@ -713,6 +717,7 @@ export const STANDARD_FLAT_OBJECT_METADATA_BUILDERS_BY_OBJECT_NAME = {
context: {
universalIdentifier: STANDARD_OBJECTS.workflow.universalIdentifier,
nameSingular: 'workflow',
openRecordIn: ObjectOpenRecordIn.RECORD_PAGE,
namePlural: 'workflows',
labelSingular: i18nLabel(msg`Workflow`),
labelPlural: i18nLabel(msg`Workflows`),
@@ -803,6 +808,7 @@ export const STANDARD_FLAT_OBJECT_METADATA_BUILDERS_BY_OBJECT_NAME = {
universalIdentifier:
STANDARD_OBJECTS.workflowVersion.universalIdentifier,
nameSingular: 'workflowVersion',
openRecordIn: ObjectOpenRecordIn.RECORD_PAGE,
namePlural: 'workflowVersions',
labelSingular: i18nLabel(msg`Workflow Version`),
labelPlural: i18nLabel(msg`Workflow Versions`),
@@ -1,4 +1,5 @@
import { STANDARD_OBJECTS } from 'twenty-shared/metadata';
import { ObjectOpenRecordIn } from 'twenty-shared/types';
import { TWENTY_STANDARD_APPLICATION_UNIVERSAL_IDENTIFIER } from 'twenty-shared/application';
import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
@@ -19,6 +20,7 @@ export type CreateStandardObjectContext<O extends AllStandardObjectName> = {
isAuditLogged?: boolean;
isUIEditable?: boolean;
isUICreatable?: boolean;
openRecordIn?: ObjectOpenRecordIn;
shortcut?: string | null;
duplicateCriteria?: string[][] | null;
labelIdentifierFieldMetadataName: AllStandardObjectFieldName<O>;
@@ -48,6 +50,7 @@ export const createStandardObjectFlatMetadata = <
isAuditLogged = true,
isUIEditable = true,
isUICreatable = true,
openRecordIn = ObjectOpenRecordIn.USER_CHOICE,
shortcut = null,
duplicateCriteria = null,
labelIdentifierFieldMetadataName,
@@ -90,6 +93,7 @@ export const createStandardObjectFlatMetadata = <
isAuditLogged,
isUIEditable,
isUICreatable,
openRecordIn,
isLabelSyncedWithName: false,
overrides: null,
duplicateCriteria,
@@ -28,6 +28,7 @@ type Assertions = [
keyof UniversalFlatEntityUpdate<'objectMetadata'>,
| 'icon'
| 'color'
| 'openRecordIn'
| 'description'
| 'isActive'
| 'overrides'
@@ -68,6 +68,7 @@ exports[`deleteFlatEntityForeignKeyAggregators should strip raw workspace-scoped
"namePlural": "defaultflatObjectMetadataNamePlural",
"nameSingular": "defaultflatObjectMetadataNameSingular",
"objectPermissionUniversalIdentifiers": [],
"openRecordIn": "USER_CHOICE",
"overrides": null,
"searchFieldMetadataUniversalIdentifiers": [],
"shortcut": "shortcut",