diff --git a/clients/tui/__tests__/schemaToForm.test.ts b/clients/tui/__tests__/schemaToForm.test.ts index 5ade85ae..2b33eae1 100644 --- a/clients/tui/__tests__/schemaToForm.test.ts +++ b/clients/tui/__tests__/schemaToForm.test.ts @@ -57,13 +57,34 @@ describe("schemaToForm", () => { }); }); - it("builds a select field from an array-of-enum", () => { + it("builds a select field from an array-of-enum on items.enum alone", () => { + const form = schemaToForm( + { + properties: { + // Standard array-of-enums shape: options come from `items.enum` with + // NO top-level `enum`. The array branch keys on `items.enum` alone + // (matching the web guard), so this renders as a select. + tags: { + type: "array", + items: { enum: ["a", "b"] }, + }, + }, + }, + "arrayEnum", + ); + expect(form.sections[0]!.fields[0]).toMatchObject({ + type: "select", + options: [ + { label: "a", value: "a" }, + { label: "b", value: "b" }, + ], + }); + }); + + it("still builds a select for an array-of-enum that also carries a top-level enum", () => { const form = schemaToForm( { properties: { - // The array branch is nested under the outer `enum` guard, so a - // top-level `enum` must also be present for it to be reached; the - // options are taken from `items.enum`. tags: { type: "array", enum: ["a", "b"], @@ -71,7 +92,7 @@ describe("schemaToForm", () => { }, }, }, - "arrayEnum", + "arrayEnumRedundant", ); expect(form.sections[0]!.fields[0]).toMatchObject({ type: "select", @@ -133,7 +154,6 @@ describe("schemaToForm", () => { properties: { pets: { type: "array", - enum: ["pet-1", "pet-2"], items: { enum: ["pet-1", "pet-2"], enumNames: ["Cats", "Dogs"] }, }, }, @@ -155,7 +175,6 @@ describe("schemaToForm", () => { properties: { pets: { type: "array", - enum: ["pet-1", "pet-2"], items: { enum: ["pet-1", "pet-2"], enumNames: ["Cats"] }, }, }, diff --git a/clients/tui/src/utils/schemaToForm.ts b/clients/tui/src/utils/schemaToForm.ts index fae70066..ac062ef9 100644 --- a/clients/tui/src/utils/schemaToForm.ts +++ b/clients/tui/src/utils/schemaToForm.ts @@ -69,27 +69,24 @@ export function schemaToForm( let field: FormField; - // Handle enum -> select - if (property.enum) { - if (property.type === "array" && property.items?.enum) { - // For array of enums, we'll use select but handle it differently - // Note: ink-form doesn't have multiselect, so we'll use select - field = { - type: "select", - ...baseField, - options: toSelectOptions( - property.items.enum, - property.items.enumNames, - ), - } as FormField; - } else { - // Single select - field = { - type: "select", - ...baseField, - options: toSelectOptions(property.enum, property.enumNames), - } as FormField; - } + // Handle enum -> select. Detect the array-of-enums case on `items.enum` + // alone (matching the web SchemaForm guard) — a standard array-of-enums + // schema carries no top-level `enum`, so gating on it would drop the field + // to a plain string input. + if (property.type === "array" && property.items?.enum) { + // ink-form has no multiselect, so we render a single select. + field = { + type: "select", + ...baseField, + options: toSelectOptions(property.items.enum, property.items.enumNames), + } as FormField; + } else if (property.enum) { + // Single select + field = { + type: "select", + ...baseField, + options: toSelectOptions(property.enum, property.enumNames), + } as FormField; } else { // Map JSON Schema types to ink-form types switch (property.type) { diff --git a/pr-screenshots/README.md b/pr-screenshots/README.md index 65b89d72..1cd2017a 100644 --- a/pr-screenshots/README.md +++ b/pr-screenshots/README.md @@ -1,3 +1,31 @@ +# TUI array-of-enum renders as a select on `items.enum` alone (#1751) — proof screenshots + +Follow-up to #1691. The TUI `schemaToForm` array-of-enum branch was nested under +`if (property.enum)`, so a standard array-of-enums schema — +`{ type: "array", items: { enum, enumNames } }` with **no** top-level `enum` — +fell through to a plain text input. The fix keys the array case on `items.enum` +alone (matching the web `SchemaForm`). Captured non-interactively (a standalone +render of the real `ink-form` built by the real `schemaToForm`). + +The demo schema has a `Sizes` array-of-enum field (`enum: [s,m,l]` + +`enumNames: [Small, Medium, Large]`, `default: "m"`, **no** top-level `enum`) +and a `Priority` single-enum control (has a top-level `enum`). + +![Before #1751 — the array-of-enum field is a plain text input showing the raw default "m"](tui-1751-before-string.png) + +**Before the fix** (reproducing the old nesting): `Sizes` is a plain **text +field** showing the raw default **`m`** — `enumNames` never applies because it +isn't a select at all. + +![With the #1751 fix — the array-of-enum field is a select resolving "m" to its title "Medium"](tui-1751-after-select.png) + +**With the fix:** `Sizes` is now a **select**; the default `m` resolves to its +title **`Medium`**. The `Priority` control (which has a top-level `enum`) shows +`High` in both — unchanged, confirming the reorder only affects the intended +`items.enum`-only case. + +--- + # TUI enum forms honor `enumNames` (#1691) — proof screenshots The TUI tool-test form (`schemaToForm` → `ink-form`) rendered against a legacy diff --git a/pr-screenshots/tui-1751-after-select.png b/pr-screenshots/tui-1751-after-select.png new file mode 100644 index 00000000..70b6f843 Binary files /dev/null and b/pr-screenshots/tui-1751-after-select.png differ diff --git a/pr-screenshots/tui-1751-before-string.png b/pr-screenshots/tui-1751-before-string.png new file mode 100644 index 00000000..477bff34 Binary files /dev/null and b/pr-screenshots/tui-1751-before-string.png differ