tui: detect array-of-enum on items.enum alone (#1752)
* tui: detect array-of-enum on items.enum alone (#1751) The TUI schemaToForm array-of-enum branch was nested under an outer `if (property.enum)` guard, so a standard array-of-enums schema — `{ type: "array", items: { enum: [...] } }` with no top-level `enum` — never reached it and fell through to a plain string input. The web SchemaForm already guards on `items?.enum` alone; this aligns the TUI with it. Restructure the enum handling so the array-of-enums case keys on `property.type === "array" && property.items?.enum` independently of a top-level `enum`, falling back to the single-select `property.enum` branch otherwise. The enumNames labeling from #1691 applies unchanged. Tests: the array-of-enum cases now use the standard `items.enum`-only shape (dropping the redundant top-level `enum` the old nesting required), plus a case asserting a redundant top-level `enum` still works. Closes #1751 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 * docs(pr-screenshots): TUI array-of-enum before/after proof shots (#1751) Before/after of the TUI tool form for an array-of-enums field with no top-level `enum`: before the fix it renders as a plain text field (raw "m"); with the fix it renders as a select resolving the enumNames title ("Medium"). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XNdjEPKLG637X8YmhDiEk5 --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
8c41dcd383
commit
c2b866110d
5 changed files with 72 additions and 28 deletions
|
|
@ -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(
|
const form = schemaToForm(
|
||||||
{
|
{
|
||||||
properties: {
|
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: {
|
tags: {
|
||||||
type: "array",
|
type: "array",
|
||||||
enum: ["a", "b"],
|
enum: ["a", "b"],
|
||||||
|
|
@ -71,7 +92,7 @@ describe("schemaToForm", () => {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"arrayEnum",
|
"arrayEnumRedundant",
|
||||||
);
|
);
|
||||||
expect(form.sections[0]!.fields[0]).toMatchObject({
|
expect(form.sections[0]!.fields[0]).toMatchObject({
|
||||||
type: "select",
|
type: "select",
|
||||||
|
|
@ -133,7 +154,6 @@ describe("schemaToForm", () => {
|
||||||
properties: {
|
properties: {
|
||||||
pets: {
|
pets: {
|
||||||
type: "array",
|
type: "array",
|
||||||
enum: ["pet-1", "pet-2"],
|
|
||||||
items: { enum: ["pet-1", "pet-2"], enumNames: ["Cats", "Dogs"] },
|
items: { enum: ["pet-1", "pet-2"], enumNames: ["Cats", "Dogs"] },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -155,7 +175,6 @@ describe("schemaToForm", () => {
|
||||||
properties: {
|
properties: {
|
||||||
pets: {
|
pets: {
|
||||||
type: "array",
|
type: "array",
|
||||||
enum: ["pet-1", "pet-2"],
|
|
||||||
items: { enum: ["pet-1", "pet-2"], enumNames: ["Cats"] },
|
items: { enum: ["pet-1", "pet-2"], enumNames: ["Cats"] },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -69,27 +69,24 @@ export function schemaToForm(
|
||||||
|
|
||||||
let field: FormField;
|
let field: FormField;
|
||||||
|
|
||||||
// Handle enum -> select
|
// Handle enum -> select. Detect the array-of-enums case on `items.enum`
|
||||||
if (property.enum) {
|
// alone (matching the web SchemaForm guard) — a standard array-of-enums
|
||||||
if (property.type === "array" && property.items?.enum) {
|
// schema carries no top-level `enum`, so gating on it would drop the field
|
||||||
// For array of enums, we'll use select but handle it differently
|
// to a plain string input.
|
||||||
// Note: ink-form doesn't have multiselect, so we'll use select
|
if (property.type === "array" && property.items?.enum) {
|
||||||
field = {
|
// ink-form has no multiselect, so we render a single select.
|
||||||
type: "select",
|
field = {
|
||||||
...baseField,
|
type: "select",
|
||||||
options: toSelectOptions(
|
...baseField,
|
||||||
property.items.enum,
|
options: toSelectOptions(property.items.enum, property.items.enumNames),
|
||||||
property.items.enumNames,
|
} as FormField;
|
||||||
),
|
} else if (property.enum) {
|
||||||
} as FormField;
|
// Single select
|
||||||
} else {
|
field = {
|
||||||
// Single select
|
type: "select",
|
||||||
field = {
|
...baseField,
|
||||||
type: "select",
|
options: toSelectOptions(property.enum, property.enumNames),
|
||||||
...baseField,
|
} as FormField;
|
||||||
options: toSelectOptions(property.enum, property.enumNames),
|
|
||||||
} as FormField;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// Map JSON Schema types to ink-form types
|
// Map JSON Schema types to ink-form types
|
||||||
switch (property.type) {
|
switch (property.type) {
|
||||||
|
|
|
||||||
|
|
@ -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 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 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
|
# TUI enum forms honor `enumNames` (#1691) — proof screenshots
|
||||||
|
|
||||||
The TUI tool-test form (`schemaToForm` → `ink-form`) rendered against a legacy
|
The TUI tool-test form (`schemaToForm` → `ink-form`) rendered against a legacy
|
||||||
|
|
|
||||||
BIN
pr-screenshots/tui-1751-after-select.png
Normal file
BIN
pr-screenshots/tui-1751-after-select.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 39 KiB |
BIN
pr-screenshots/tui-1751-before-string.png
Normal file
BIN
pr-screenshots/tui-1751-before-string.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 39 KiB |
Loading…
Reference in a new issue