diff --git a/packages/twenty-server/test/integration/metadata/suites/application/successful-manifest-update-field.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/application/successful-manifest-update-field.integration-spec.ts index 3e47c2ebf9..038a4a3feb 100644 --- a/packages/twenty-server/test/integration/metadata/suites/application/successful-manifest-update-field.integration-spec.ts +++ b/packages/twenty-server/test/integration/metadata/suites/application/successful-manifest-update-field.integration-spec.ts @@ -87,23 +87,37 @@ const buildReferenceFieldManifest = (isNullable: boolean): FieldManifest => ({ // coerces null/omitted TEXT values to '' via the null-equivalent processor, // so a TEXT column can never actually hold NULL. NUMBER preserves NULL, which // is what the nullable -> non-nullable backfill needs to act on. -const buildEstimateFieldManifest = ({ - isNullable, - defaultValue, -}: { - isNullable: boolean; - defaultValue?: number; -}): FieldManifest => ({ - universalIdentifier: TEST_NUMBER_FIELD_ID, - type: FieldMetadataType.NUMBER, - name: 'estimate', - label: 'Estimate', - description: 'Ticket estimate', - icon: 'IconNumber', - isNullable, - ...(isDefined(defaultValue) ? { defaultValue } : {}), - objectUniversalIdentifier: TEST_OBJECT.universalIdentifier, -}); +const buildEstimateFieldManifest = ( + params: + | { isNullable: true; defaultValue?: number } + | { isNullable: false; defaultValue: number }, +): FieldManifest => { + const commonEstimateFields = { + universalIdentifier: TEST_NUMBER_FIELD_ID, + type: FieldMetadataType.NUMBER as const, + name: 'estimate', + label: 'Estimate', + description: 'Ticket estimate', + icon: 'IconNumber', + objectUniversalIdentifier: TEST_OBJECT.universalIdentifier, + }; + + if (params.isNullable) { + return { + ...commonEstimateFields, + isNullable: true, + ...(isDefined(params.defaultValue) + ? { defaultValue: params.defaultValue } + : {}), + }; + } + + return { + ...commonEstimateFields, + isNullable: false, + defaultValue: params.defaultValue, + }; +}; const createTicketRecord = async (data: Record) => { const response = await makeGraphqlAPIRequest( @@ -415,7 +429,7 @@ describe('Manifest update - fields', () => { description: 'Unique external identifier', icon: 'IconId', isUnique: true, - isNullable: false, + isNullable: true, objectUniversalIdentifier: TEST_OBJECT.universalIdentifier, }, ], diff --git a/packages/twenty-shared/src/application/fieldManifestType.ts b/packages/twenty-shared/src/application/fieldManifestType.ts index 75c4486a39..1d93f3f4f9 100644 --- a/packages/twenty-shared/src/application/fieldManifestType.ts +++ b/packages/twenty-shared/src/application/fieldManifestType.ts @@ -7,7 +7,7 @@ import { type RelationAndMorphRelationFieldMetadataType, } from '@/types'; -export type RegularFieldManifest< +type BaseRegularFieldManifest< T extends FieldMetadataType = Exclude< FieldMetadataType, RelationAndMorphRelationFieldMetadataType @@ -18,36 +18,37 @@ export type RegularFieldManifest< label: string; description?: string; icon?: string; - /** - * Default value in the canonical metadata format. - * - * Literal string defaults must be wrapped in single quotes inside the - * string (e.g. `"'Draft'"`), including string sub-fields of composite - * defaults (e.g. `{ source: "'MANUAL'" }`) and SELECT/MULTI_SELECT values. - * Unquoted strings are reserved for computed defaults such as `'uuid'` - * and `'now'`; any other unquoted string raises a validation warning. - */ - defaultValue?: FieldMetadataDefaultValue; options?: FieldMetadataOptions; universalSettings?: FieldMetadataUniversalSettings; - isNullable?: boolean; - // When false, this field is not editable through the generic UI isUIEditable?: boolean; - /** - * @deprecated Use defineIndex({ isUnique: true, fields: [...] }) instead. - * Indexes are the SDK primitive for uniqueness — they support both single- - * and multi-column unique constraints with a single, consistent API. This - * field still works but will be removed in a future release. - */ isUnique?: boolean; objectUniversalIdentifier: string; }; +type RegularFieldManifestNullability = + | { + defaultValue: FieldMetadataDefaultValue; + isNullable?: boolean; + } + | { + defaultValue?: FieldMetadataDefaultValue; + isNullable?: true; + }; + +export type RegularFieldManifest< + T extends FieldMetadataType = Exclude< + FieldMetadataType, + RelationAndMorphRelationFieldMetadataType + >, +> = BaseRegularFieldManifest & RegularFieldManifestNullability; + export type RelationFieldManifest< T extends RelationAndMorphRelationFieldMetadataType = RelationAndMorphRelationFieldMetadataType, -> = Omit, 'universalSettings' | 'type'> & { +> = Omit, 'universalSettings' | 'type'> & { type: T; + isNullable?: boolean; + defaultValue?: FieldMetadataDefaultValue; relationTargetFieldMetadataUniversalIdentifier: string; relationTargetObjectMetadataUniversalIdentifier: string; universalSettings: FieldMetadataUniversalSettings; diff --git a/packages/twenty-ui/src/input/Slider/Slider.tsx b/packages/twenty-ui/src/input/Slider/Slider.tsx index d8adf3b85c..f79996b0a9 100644 --- a/packages/twenty-ui/src/input/Slider/Slider.tsx +++ b/packages/twenty-ui/src/input/Slider/Slider.tsx @@ -40,7 +40,6 @@ const getSliderProgress = ({ return Math.min(1, Math.max(0, (value - min) / (max - min))); }; - const getSliderFill = ({ max, min,