autoFixSpec prunes children references to undefined elements

Dangling references are the dominant remaining first-attempt validation
failure in benchmarks, and models frequently fail to repair them even given
the exact error (observed: three repair turns, same dangling footer each
time). The renderer already skips missing children at runtime, so pruning
yields the identical rendered output while letting the spec validate. Each
removal is reported in fixes.
This commit is contained in:
Chris Tate
2026-06-10 15:04:57 -05:00
parent c731a9c607
commit c26d3b08d3
2 changed files with 46 additions and 0 deletions
+29
View File
@@ -148,6 +148,35 @@ describe("validateSpec", () => {
// =============================================================================
describe("autoFixSpec", () => {
it("prunes children references to undefined elements", () => {
const spec: Spec = {
root: "root",
elements: {
root: { type: "Card", props: {}, children: ["text", "ghost"] },
text: { type: "Text", props: { text: "hi" }, children: [] },
},
};
const { spec: fixed, fixes } = autoFixSpec(spec);
expect(fixed.elements.root!.children).toEqual(["text"]);
expect(fixes).toEqual([
'Removed reference to undefined element "ghost" from children of "root".',
]);
expect(validateSpec(fixed).valid).toBe(true);
});
it("leaves intact children untouched", () => {
const spec: Spec = {
root: "root",
elements: {
root: { type: "Card", props: {}, children: ["text"] },
text: { type: "Text", props: { text: "hi" }, children: [] },
},
};
const { spec: fixed, fixes } = autoFixSpec(spec);
expect(fixed.elements.root!.children).toEqual(["text"]);
expect(fixes).toEqual([]);
});
it("moves visible from props to element level", () => {
const spec: Spec = {
root: "root",
+17
View File
@@ -277,6 +277,23 @@ export function autoFixSpec(spec: Spec): {
fixedElements[key] = fixed;
}
// Drop references to elements that were never defined. The renderer skips
// missing children at runtime, so pruning produces the same rendered output
// while letting the spec pass validation instead of hard-failing.
for (const [key, element] of Object.entries(fixedElements)) {
if (!element.children || element.children.length === 0) continue;
const present = element.children.filter((child) => child in fixedElements);
if (present.length === element.children.length) continue;
for (const child of element.children) {
if (!(child in fixedElements)) {
fixes.push(
`Removed reference to undefined element "${child}" from children of "${key}".`,
);
}
}
fixedElements[key] = { ...element, children: present };
}
return {
spec: { root: spec.root, elements: fixedElements, state: spec.state },
fixes,