Fix array-typed parameters in code/logic-function action forms (#21102)

## Problem

`any[]` type prevented value input:
<img width="544" height="349" alt="Screenshot 2026-06-01 at 14 08 47"
src="https://github.com/user-attachments/assets/956238d0-6fea-4be1-b75a-ab0e6e6424ac"
/>

In the workflow Code action (and the Logic Function action), parameters
typed as any[], string[], etc. rendered as an empty grey box instead of
an "Enter value" text input. After any debounced save, even a properly
initialised array field would also collapse into an empty container.

The Array<T> / ReadonlyArray<T> generic form fell through to a generic
text input by accident (which "looked" right, but for the wrong reason —
no schema info downstream).

## Root causes
Three places treated arrays as plain objects via @sniptt/guards'
isObject (which is true for arrays):

1. WorkflowEditActionCodeFields.tsx — arrays went into the nested-fields
branch; Object.entries([]) is empty → empty container, no placeholder.
2. mergeDefaultFunctionInputAndFunctionInput.ts — recursed into arrays
during merge, turning [] into {}. Triggered on every debounced save, so
the bug surfaced after any edit.
3. get-function-input-schema.ts — only handled T[]
(SyntaxKind.ArrayType); Array<T> (SyntaxKind.TypeReference) was
unrecognised, so the form lost any item-type info.
This commit is contained in:
Marie
2026-06-01 15:14:46 +02:00
committed by GitHub
parent da5e1152bb
commit 66afd5a1de
6 changed files with 127 additions and 7 deletions
@@ -10,8 +10,8 @@ import { getWorkflowCodeFieldsEnumSelectOptions } from '@/workflow/workflow-step
import { getWorkflowCodeFieldsLeafKind } from '@/workflow/workflow-steps/workflow-actions/code-action/utils/getWorkflowCodeFieldsLeafKind';
import { styled } from '@linaria/react';
import { t } from '@lingui/core/macro';
import { isNonEmptyArray, isNonEmptyString, isObject } from '@sniptt/guards';
import { isDefined } from 'twenty-shared/utils';
import { isNonEmptyArray, isNonEmptyString } from '@sniptt/guards';
import { isDefined, isPlainObject } from 'twenty-shared/utils';
import { type FunctionInput, type InputSchema } from 'twenty-shared/workflow';
import { themeCssVariables } from 'twenty-ui/theme-constants';
@@ -59,7 +59,7 @@ export const WorkflowEditActionCodeFields = ({
? schemaProperty.label
: inputKey;
if (inputValue !== null && isObject(inputValue)) {
if (isPlainObject(inputValue)) {
return (
<div key={pathKey}>
<InputLabel>{displayLabel}</InputLabel>
@@ -24,4 +24,28 @@ describe('mergeDefaultFunctionInputAndFunctionInput', () => {
}),
).toEqual(expectedResult);
});
it('should preserve array values without recursing into them as nested objects', () => {
const newInput = { briefs: [], b: null };
const oldInput = { briefs: [], b: 5 };
expect(
mergeDefaultFunctionInputAndFunctionInput({
newInput,
oldInput,
}),
).toEqual({ briefs: [], b: 5 });
});
it('should keep an array typed by the user instead of resetting it', () => {
const newInput = { briefs: [], b: null };
const oldInput = { briefs: '["a", "b"]', b: null };
expect(
mergeDefaultFunctionInputAndFunctionInput({
newInput,
oldInput,
}),
).toEqual({ briefs: '["a", "b"]', b: null });
});
});
@@ -1,5 +1,5 @@
import { isPlainObject } from 'twenty-shared/utils';
import { type FunctionInput } from 'twenty-shared/workflow';
import { isObject } from '@sniptt/guards';
export const mergeDefaultFunctionInputAndFunctionInput = ({
newInput,
@@ -16,12 +16,12 @@ export const mergeDefaultFunctionInputAndFunctionInput = ({
if (!(key in oldInput)) {
result[key] = newValue;
} else if (newValue === null && isObject(oldValue)) {
} else if (newValue === null && isPlainObject(oldValue)) {
result[key] = null;
} else if (isObject(newValue)) {
} else if (isPlainObject(newValue)) {
result[key] = mergeDefaultFunctionInputAndFunctionInput({
newInput: newValue,
oldInput: isObject(oldValue) ? oldValue : {},
oldInput: isPlainObject(oldValue) ? oldValue : {},
});
} else {
result[key] = oldValue;