From 5f6bb7a5bae4e6d8809823920d67e52d6ba9f963 Mon Sep 17 00:00:00 2001 From: Kaoru0822 <208281322+kaoru0822-kitauji@users.noreply.github.com> Date: Sat, 10 Oct 2026 00:31:00 -0700 Subject: [PATCH] perf(apple): state each NIB object's class, label, and ID once (#1477) The NIBArchive projection synthesizes customClass, label, and objectID so its records share the ibtool dictionary parser. The parser then returned those values as class_name, name, and the object ID, and the graph node kept the synthesized copies in attributes as well. Attributes now omit a key whose value its canonical field already carries; differing authored values stay. Transmission.app decode_interface_builder output shrinks from 6.45 MB to 6.14 MB. Co-authored-by: Claude Opus 5.5 --- .../apple/interfaceBuilderGraph.test.ts | 29 +++++++++++++++++++ .../apple/interfaceBuilderKeyedArchive.ts | 12 +++++++- 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/src/domain/apple/interfaceBuilderGraph.test.ts b/src/domain/apple/interfaceBuilderGraph.test.ts index fb1c0b9ad..bfa2c71de 100644 --- a/src/domain/apple/interfaceBuilderGraph.test.ts +++ b/src/domain/apple/interfaceBuilderGraph.test.ts @@ -552,3 +552,32 @@ it("names the archive once per evidence record", () => { evidence.filter(({ description }) => description.includes(relativePath)), ).toEqual([]); }); + +it("states each object's class, label, and ID once", () => { + const { objects } = parseInterfaceBuilderRecords({ + "com.apple.ibtool.document.objects": { + "7": { + customClass: "NSButton", + label: "Start", + objectID: "7", + nibValues: { NSContents: "Start" }, + }, + button: { class: "UIButton", title: "Build", objectID: "authored" }, + }, + }); + expect(objects).toEqual([ + expect.objectContaining({ + id: "7", + class_name: "NSButton", + name: "Start", + attributes: { nibValues: { NSContents: "Start" } }, + }), + expect.objectContaining({ + id: "button", + class_name: "UIButton", + name: "Build", + // Values that differ from the canonical fields stay as authored. + attributes: { class: "UIButton", title: "Build", objectID: "authored" }, + }), + ]); +}); diff --git a/src/domain/apple/interfaceBuilderKeyedArchive.ts b/src/domain/apple/interfaceBuilderKeyedArchive.ts index 51b446b52..c0b7f3507 100644 --- a/src/domain/apple/interfaceBuilderKeyedArchive.ts +++ b/src/domain/apple/interfaceBuilderKeyedArchive.ts @@ -71,13 +71,23 @@ export const parseInterfaceBuilderRecords = ( attributes["name"], className, ); + // class_name, name, and id already carry these values; repeating them in + // attributes doubled them on every graph node. + const carried = new Set(); + if (attributes["customClass"] === className) carried.add("customClass"); + if (attributes["label"] === label) carried.add("label"); + if (attributes["objectID"] === id) carried.add("objectID"); objects.push( objectNode.parse({ id, kind: classifyObject(className), class_name: className, name: label ?? id, - attributes: jsonSafeRecord(attributes), + attributes: jsonSafeRecord( + Object.fromEntries( + Object.entries(attributes).filter(([key]) => !carried.has(key)), + ), + ), }), ); }