diff --git a/clients/web/src/components/groups/SchemaForm/SchemaForm.test.tsx b/clients/web/src/components/groups/SchemaForm/SchemaForm.test.tsx index 8e42c452..9aa55d23 100644 --- a/clients/web/src/components/groups/SchemaForm/SchemaForm.test.tsx +++ b/clients/web/src/components/groups/SchemaForm/SchemaForm.test.tsx @@ -2355,6 +2355,42 @@ describe("SchemaForm multiline strings (#2042)", () => { ); }); + it("re-derives the selection when the branches are reordered in place", () => { + // Same `resetKey`, same branch count — only the order changed, so a + // numeric index would now point at the other shape and the picker would + // show SMS while `values` describe email. + const reversed: InspectorFormSchema = { + ...UNION_SCHEMA, + anyOf: [...(UNION_SCHEMA.anyOf ?? [])].reverse(), + }; + const values = { note: "hi", kind: "email", address: "a@b.c" }; + const { rerender } = renderWithMantine( + , + ); + expect(screen.getByRole("textbox", { name: /Address/ })).toBeTruthy(); + + rerender( + , + ); + // Still the email branch — the one the values describe — not whatever + // now sits at the old index. + expect( + (screen.getByRole("textbox", { name: /Variant/ }) as HTMLInputElement) + .value, + ).toBe("email"); + expect(screen.getByRole("textbox", { name: /Address/ })).toBeTruthy(); + }); + it("renders no picker for a single-branch union but still shows its fields", () => { const schema: InspectorFormSchema = { type: "object", diff --git a/clients/web/src/components/groups/SchemaForm/SchemaForm.tsx b/clients/web/src/components/groups/SchemaForm/SchemaForm.tsx index 047502bb..24d978db 100644 --- a/clients/web/src/components/groups/SchemaForm/SchemaForm.tsx +++ b/clients/web/src/components/groups/SchemaForm/SchemaForm.tsx @@ -623,6 +623,18 @@ export function SchemaForm({ const [branchIndex, setBranchIndex] = useState( () => selectBranchIndex(branches, values) ?? 0, ); + // The alternatives themselves, as a stable key. A tool refreshed in place + // keeps its `resetKey`, so nothing else notices that the union underneath was + // reordered or rewritten — and a numeric index then points at a different + // branch than the one whose values are held, showing SMS while submitting + // email. Re-derived from the values, which is where the answer actually is. + const branchesKey = branches + .map((branch) => `${branch.label}:${branch.declaredFields.join(",")}`) + .join("|"); + useValueChange(branchesKey, () => + setBranchIndex(selectBranchIndex(branches, values) ?? 0), + ); + // A form reused for another entity can be handed a shorter union, so the // index is clamped rather than trusted — `resetKey` resets it below, but a // caller that omits it (the elicitation panels mount fresh) supplies none.