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
47 changes: 47 additions & 0 deletions .changeset/8632-malformed-picklist-option-loud.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
---
'@object-ui/app-shell': minor
---

Report a malformed picklist option in the field designer instead of showing it as a
blank row and deleting it (objectui#8632).

**The half that did the damage is the deletion.** `ObjectFieldInspector`'s
`readOptions` opened with `value: String(o?.value ?? '')`, so every option entry it
could not read arrived as `value: ''` — two empty input boxes. `OptionsEditor` persists
only rows with a non-empty `value`, so those entries were then written out of the
document. Measured on `options: ['draft','open','closed']`: the list rendered as three
blank rows, and **one click on "Add value", with nothing typed, wrote `options: []`**.
An author who opened a picklist, saw an empty-looking option list, and clicked the
obvious button lost three authored options they had never been shown, with nothing on
screen attributing the loss to anything they did.

**"Malformed" was two families, not one shape.** Beyond the entries that collapsed to a
blank row (a bare string, `null`, a number, a boolean, `{}`, an option with no `value`,
an authored empty `value`, a nested array), a second family was silently **rewritten**
and never looked wrong: `{ value: 5 }` was written back as `"5"`, `{ value: ['alpha'] }`
as `"alpha"`, `{ value: { a: 1 } }` as `"[object Object]"`, a non-string `label` as
`label: ''`, and a non-string `color` was dropped from the document. Both families are
now covered by one rule.

**What changed.** The reader is now strict — the `String()` coercion is gone rather than
widened, per AGENTS.md #0.1 — and classifies each authored entry. An entry this editor
cannot represent faithfully gets a marked row of its own naming the reason, showing the
authored entry verbatim, and carrying the same reorder/remove controls as any other row;
it is written back **byte for byte as authored** on every commit. Removing it stays
available and stays deliberate.

**This narrows what the designer will save.** A document with a malformed option used to
become saveable because the designer silently deleted the offending entries; it no longer
does. The entry is preserved, so the draft keeps failing `FieldSchema` until the author
repairs or removes it — which is the reported state rather than a silent repair. The
escape path is one click on the row's Remove button, and it reproduces exactly the old
outcome with the author choosing it.

Well-formed option sets are untouched: they render and commit key for key as before,
including the `default` / `visibleWhen` carrier (objectui#7540), the `label: ''` emitted
for an option with no `label` key (objectui#7014), and the editor's own blank trailing
row, which is still filtered on commit.

Two new strings land in the designer's own `en` / `zh` tables — the metadata-admin
console owns its strings in `views/metadata-admin/i18n.ts` and is deliberately outside
the ten locale packs (`packages/i18n/README.md`, "Scope — the `engine.*` carve-out").
14 changes: 14 additions & 0 deletions packages/app-shell/src/views/metadata-admin/i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1392,6 +1392,13 @@ const ENGINE_STRINGS_EN: Record<string, string> = {
'designer.field.noGroup': '— No group —',
'designer.field.picklistValues': 'Picklist values',
'designer.field.noValues': 'No values yet.',
'designer.field.optMalformed': 'This option cannot be edited here',
'designer.field.optMalformed.notAnObject': 'It is not an option object.',
'designer.field.optMalformed.valueNotText': 'Its `value` is missing or is not text.',
'designer.field.optMalformed.valueEmpty': 'Its `value` is empty.',
'designer.field.optMalformed.labelNotText': 'Its `label` is not text.',
'designer.field.optMalformed.colorNotText': 'Its `color` is not text.',
'designer.field.optMalformedHint': 'It is kept exactly as authored. Repair it in the JSON source, or remove it here.',
'designer.field.addValue': 'Add value',
'designer.field.optValue': 'value',
'designer.field.optLabel': 'Label',
Expand Down Expand Up @@ -3336,6 +3343,13 @@ const ENGINE_STRINGS_ZH: Record<string, string> = {
'designer.field.noGroup': '— 无分组 —',
'designer.field.picklistValues': '选项值',
'designer.field.noValues': '暂无选项值。',
'designer.field.optMalformed': '此选项无法在这里编辑',
'designer.field.optMalformed.notAnObject': '它不是一个选项对象。',
'designer.field.optMalformed.valueNotText': '它的 `value` 缺失或不是文本。',
'designer.field.optMalformed.valueEmpty': '它的 `value` 为空。',
'designer.field.optMalformed.labelNotText': '它的 `label` 不是文本。',
'designer.field.optMalformed.colorNotText': '它的 `color` 不是文本。',
'designer.field.optMalformedHint': '它按原样保留。请在 JSON 源码中修复,或在此处删除。',
'designer.field.addValue': '添加选项',
'designer.field.optValue': '值',
'designer.field.optLabel': '显示名',
Expand Down
Loading
Loading