@@ -34,7 +34,11 @@ import type {
3434 UpdateColumnOptionsData ,
3535 UpdateColumnTypeData ,
3636} from '@/lib/table/types'
37- import { resolveSelectOptionId , validateColumnDefinition } from '@/lib/table/validation'
37+ import {
38+ resolveSelectOptionId ,
39+ splitMultiSelectInput ,
40+ validateColumnDefinition ,
41+ } from '@/lib/table/validation'
3842import { assertValidSchema , stripGroupDeps } from '@/lib/table/workflow-columns'
3943
4044const logger = createLogger ( 'TableColumnService' )
@@ -881,11 +885,25 @@ async function migrateCellsToSelectIds(
881885 )
882886
883887 if ( multiple ) {
888+ // A string cell reaching a multi target is either one option name or the
889+ // comma-joined form a multiselect converts to text as. Try the whole string
890+ // first so an option whose own name contains a comma still wins, then split
891+ // — mirroring `splitMultiSelectInput` on the write path, including its
892+ // first-occurrence dedup.
884893 await trx . execute (
885894 sql `UPDATE ${ userTableRows }
886895 SET data = jsonb_set(data, ARRAY[${ columnKey } ::text],
887896 CASE WHEN data->>${ columnKey } ::text = '' THEN '[]'::jsonb
888- ELSE jsonb_build_array(COALESCE(${ idByRef } ::jsonb -> (data->>${ columnKey } ::text), ${ idByRef } ::jsonb -> lower(data->>${ columnKey } ::text), data->${ columnKey } ::text))
897+ WHEN COALESCE(${ idByRef } ::jsonb -> (data->>${ columnKey } ::text), ${ idByRef } ::jsonb -> lower(data->>${ columnKey } ::text)) IS NOT NULL
898+ THEN jsonb_build_array(COALESCE(${ idByRef } ::jsonb -> (data->>${ columnKey } ::text), ${ idByRef } ::jsonb -> lower(data->>${ columnKey } ::text)))
899+ ELSE COALESCE((
900+ SELECT jsonb_agg(v ORDER BY ord) FROM (
901+ SELECT COALESCE(${ idByRef } ::jsonb -> btrim(part), ${ idByRef } ::jsonb -> lower(btrim(part)), to_jsonb(btrim(part))) AS v,
902+ min(o) AS ord
903+ FROM unnest(string_to_array(data->>${ columnKey } ::text, ',')) WITH ORDINALITY AS u(part, o)
904+ WHERE btrim(part) <> ''
905+ GROUP BY 1
906+ ) d), '[]'::jsonb)
889907 END)
890908 WHERE table_id = ${ tableId }
891909 AND jsonb_typeof(data->${ columnKey } ::text) = 'string'`
@@ -1004,7 +1022,15 @@ export function isValueCompatibleWithType(
10041022 case 'select' : {
10051023 // A cleared select cell is written as '' — still convertible.
10061024 if ( value === '' ) return true
1007- const parts = Array . isArray ( value ) ? value : [ value ]
1025+ // Read the value exactly as the write-path coercion will. A multi target
1026+ // splits a comma-delimited string, so a multiselect → text → multiselect
1027+ // round-trip (text holding this feature's own `Bug, Docs` export shape)
1028+ // stays convertible instead of being rejected as one unknown option.
1029+ const parts = targetMultiple
1030+ ? splitMultiSelectInput ( value as JsonValue )
1031+ : Array . isArray ( value )
1032+ ? value
1033+ : [ value ]
10081034 // A single-select target can't hold several options. `updateColumnOptions`
10091035 // blocks the same transition; without this the next coerce would silently
10101036 // keep only the first id.
0 commit comments