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.