Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions .changeset/kanban-requiredwhen-prompt-4254.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
'@object-ui/plugin-kanban': minor
'@object-ui/i18n': patch
---

Kanban: a drop that makes fields required now collects them instead of dead-ending

Dragging a card into a column whose value flips a field's `requiredWhen` predicate to TRUE used to PATCH the column value alone. The engine refused the whole update — correctly, that is what the predicate declares — and the board had no way to finish the move: the only path to closing a won deal was to abandon the board and open the record form. HotCRM's opportunity pipeline is the reported case (`win_reason` is required when `stage == "closed_won"`), but the dead end belonged to every board whose target column carries a conditional requirement.

The board now evaluates the target column's predicates BEFORE writing anything. If the move would make fields required while they are still empty, it opens a small dialog collecting exactly those fields, then submits the column value and everything collected as ONE PATCH — never two writes, which would leave the record in the refused state if the second one failed. A drop that triggers no predicate is untouched, down to the PATCH body.

The verdict comes from `@object-ui/core`'s `resolveFieldRuleState` — the same evaluator the record form, the wizard and the line-item grid already resolve `visibleWhen`/`readonlyWhen`/`requiredWhen` with, delegating to `@objectstack/formula`'s CEL engine. The board's prompt and the server's enforcement therefore reach the identical verdict rather than drifting through a second hand-rolled predicate evaluator. Emptiness is core's `isMissingForRequired`, the presence contract the form and the server share, so a `false` boolean and a `0` count as answers and are not re-asked.

Every control in the dialog is `@object-ui/fields`' `FieldEditWidget`, the same widget the record form renders for that field type — a select edits as a select, a date as a date picker — so this adds no second set of field-rendering decisions.

Four kinds of field are deliberately NOT collected, and each falls through to the unchanged PATCH where the server's refusal (legible since objectstack#7525) speaks for itself: one that already has a value, one `visibleWhen` hides, one that is readonly, and one whose type has no edit widget at all. A dialog row with no control would be a worse dead end than the one being fixed.

Cancelling writes nothing and leaves the card in its original column; a combined PATCH that is still refused for some other reason surfaces the refusal and rolls back exactly as a plain rejected move does, rather than looping the dialog on an arbitrary server error.

`@object-ui/i18n` carries two new `kanban.*` strings for the dialog, translated across all ten packs. Its public type surface is unchanged — the `.d.ts` was measured identical before and after — hence the patch bump.
2 changes: 2 additions & 0 deletions packages/i18n/src/locales/ar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -631,6 +631,8 @@ const ar = {
deleteColumn: "حذف عمود",
noCards: "لا بطاقات",
cardTitlePlaceholder: "أدخل عنوان البطاقة…",
requiredFieldsTitle: "أكمل الحقول المطلوبة",
requiredFieldsDescription: "يجعل هذا النقل الحقول أدناه مطلوبة. أكملها للمتابعة.",
},
timeline: {
bucket: {
Expand Down
2 changes: 2 additions & 0 deletions packages/i18n/src/locales/de.ts
Original file line number Diff line number Diff line change
Expand Up @@ -627,6 +627,8 @@ const de = {
deleteColumn: "Spalte löschen",
noCards: "Keine Karten",
cardTitlePlaceholder: "Kartentitel eingeben…",
requiredFieldsTitle: "Erforderliche Felder ausfüllen",
requiredFieldsDescription: "Durch diese Verschiebung werden die folgenden Felder zu Pflichtfeldern. Füllen Sie sie aus, um fortzufahren.",
},
timeline: {
bucket: {
Expand Down
2 changes: 2 additions & 0 deletions packages/i18n/src/locales/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -676,6 +676,8 @@ const en = {
cardTitlePlaceholder: 'Enter card title…',
uncategorized: 'Uncategorized',
columns: 'columns',
requiredFieldsTitle: 'Complete required fields',
requiredFieldsDescription: 'This move makes the fields below required. Fill them in to continue.',
},
timeline: {
bucket: {
Expand Down
2 changes: 2 additions & 0 deletions packages/i18n/src/locales/es.ts
Original file line number Diff line number Diff line change
Expand Up @@ -631,6 +631,8 @@ const es = {
deleteColumn: "Eliminar columna",
noCards: "Sin tarjetas",
cardTitlePlaceholder: "Escriba el título de la tarjeta…",
requiredFieldsTitle: "Complete los campos obligatorios",
requiredFieldsDescription: "Este movimiento hace que los campos siguientes sean obligatorios. Complételos para continuar.",
},
timeline: {
bucket: {
Expand Down
2 changes: 2 additions & 0 deletions packages/i18n/src/locales/fr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -627,6 +627,8 @@ const fr = {
deleteColumn: "Supprimer la colonne",
noCards: "Aucune carte",
cardTitlePlaceholder: "Saisir le titre de la carte…",
requiredFieldsTitle: "Compléter les champs obligatoires",
requiredFieldsDescription: "Ce déplacement rend les champs ci-dessous obligatoires. Remplissez-les pour continuer.",
},
timeline: {
bucket: {
Expand Down
2 changes: 2 additions & 0 deletions packages/i18n/src/locales/ja.ts
Original file line number Diff line number Diff line change
Expand Up @@ -627,6 +627,8 @@ const ja = {
deleteColumn: "カラムを削除",
noCards: "カードがありません",
cardTitlePlaceholder: "カードのタイトルを入力…",
requiredFieldsTitle: "必須項目を入力してください",
requiredFieldsDescription: "この移動により以下の項目が必須になります。入力して続行してください。",
},
timeline: {
bucket: {
Expand Down
2 changes: 2 additions & 0 deletions packages/i18n/src/locales/ko.ts
Original file line number Diff line number Diff line change
Expand Up @@ -627,6 +627,8 @@ const ko = {
deleteColumn: "열 삭제",
noCards: "카드 없음",
cardTitlePlaceholder: "카드 제목 입력…",
requiredFieldsTitle: "필수 항목 입력",
requiredFieldsDescription: "이 이동으로 아래 항목이 필수가 됩니다. 입력한 후 계속하세요.",
},
timeline: {
bucket: {
Expand Down
2 changes: 2 additions & 0 deletions packages/i18n/src/locales/pt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -626,6 +626,8 @@ const pt = {
deleteColumn: "Excluir coluna",
noCards: "Sem cartões",
cardTitlePlaceholder: "Digite o título do cartão…",
requiredFieldsTitle: "Preencha os campos obrigatórios",
requiredFieldsDescription: "Esta movimentação torna os campos abaixo obrigatórios. Preencha-os para continuar.",
},
timeline: {
bucket: {
Expand Down
2 changes: 2 additions & 0 deletions packages/i18n/src/locales/ru.ts
Original file line number Diff line number Diff line change
Expand Up @@ -633,6 +633,8 @@ const ru = {
deleteColumn: "Удалить колонку",
noCards: "Нет карточек",
cardTitlePlaceholder: "Введите название карточки…",
requiredFieldsTitle: "Заполните обязательные поля",
requiredFieldsDescription: "Это перемещение делает поля ниже обязательными. Заполните их, чтобы продолжить.",
},
timeline: {
bucket: {
Expand Down
2 changes: 2 additions & 0 deletions packages/i18n/src/locales/zh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -648,6 +648,8 @@ const zh = {
cardTitlePlaceholder: '输入卡片标题…',
uncategorized: '未分类',
columns: '列',
requiredFieldsTitle: '填写必填字段',
requiredFieldsDescription: '此次移动会使以下字段成为必填项。填写后即可继续。',
},
timeline: {
bucket: {
Expand Down
Loading
Loading