fix: address Copilot review round 11 — recurse constants into nested objects

applySchemaConstants only corrected top-level pinned fields, but the deep
link's overlay replaces a nested object wholesale rather than merging into it —
so `{ config: { kind: "sms" } }` slipped past a nested read-only
`kind: { const: "email" }` the form was displaying. It now recurses into
object-valued properties, resolving each one's own root union the same way.

Signed-off-by: cliffhall <cliff@futurescale.com>
This commit is contained in:
cliffhall 2026-08-26 04:36:13 -04:00
commit a69cb58451
2 changed files with 49 additions and 8 deletions

View file

@ -445,6 +445,27 @@ describe("root composition (#2123)", () => {
});
});
it("re-applies a nested object's constants", () => {
// The overlay replaces the whole nested object rather than merging into it,
// so a link naming `{ config: { kind: "sms" } }` would otherwise slip past
// the pinned `kind` the nested form displays.
const schema: InspectorFormSchema = {
type: "object",
properties: {
config: {
type: "object",
properties: {
kind: { type: "string", const: "email" },
to: { type: "string" },
},
},
},
};
expect(
applySchemaConstants(schema, { config: { kind: "sms", to: "a@b.c" } }),
).toEqual({ config: { kind: "email", to: "a@b.c" } });
});
it("leaves values alone when nothing is pinned", () => {
const values = { a: 1 };
expect(

View file

@ -194,14 +194,34 @@ export function applySchemaConstants(
const { base, branches } = resolveRootUnion(schema);
const selected = selectBranchIndex(branches, values) ?? 0;
const properties = (branches[selected]?.schema ?? base).properties ?? {};
const pinned = Object.entries(properties).filter(
([, fieldSchema]) => fieldSchema.const !== undefined,
);
if (pinned.length === 0) return values;
return Object.fromEntries([
...Object.entries(values),
...pinned.map(([name, fieldSchema]) => [name, fieldSchema.const]),
]);
const corrections: [string, unknown][] = [];
for (const [name, rawSchema] of Object.entries(properties)) {
const fieldSchema = normalizeNullableUnion(rawSchema);
if (fieldSchema.const !== undefined) {
corrections.push([name, fieldSchema.const]);
continue;
}
// Recurse: a nested object renders its own read-only fields, and the
// overlay replaces the whole object rather than merging into it — so a
// link naming `{ config: { kind: "sms" } }` would otherwise slip a value
// past the pinned `kind` the nested form is displaying.
const nested = values[name];
if (
typeof nested === "object" &&
nested !== null &&
!Array.isArray(nested)
) {
const corrected = applySchemaConstants(
fieldSchema,
nested as Record<string, unknown>,
);
if (corrected !== nested) corrections.push([name, corrected]);
}
}
if (corrections.length === 0) return values;
return Object.fromEntries([...Object.entries(values), ...corrections]);
}
/**