Intro
Add a storyboard_diff.py script that performs structured diffing of Interface Builder .storyboard and .xib files. Rather than treating these as opaque XML blobs (which produce noisy, unreadable git diffs), the script would parse the XML into semantic components — view controllers, views, constraints, outlets, actions, segues — and report meaningful changes in a token-optimised format.
Value Proposition
- For AI agents: IB files produce enormous XML diffs that waste context. A structured diff reduces a 200-line XML diff to 3-5 lines of semantic changes like
Added: UIButton "Submit" in LoginViewController, Changed: constraint leading 16→24 on nameLabel.
- For developers: Storyboard merge conflicts are notoriously painful. A semantic diff helps understand what actually changed before resolving conflicts.
- For code review: PR reviewers can quickly understand IB changes without mentally parsing XML.
- Complements existing tooling: Pairs naturally with
visual_diff.py — one catches pixel-level changes, the other catches structural changes.
Prerequisites
- Python 3.9+ (already required by the project)
xml.etree.ElementTree from Python stdlib (no external dependencies needed)
- Xcode's
ibtool is available via xcrun ibtool but has limited diff capabilities — it supports --export for property extraction and --previous-file / --localize-incremental for localization deltas, but no general-purpose structural diff. Our script fills this gap.
Assumptions
- Target users are working with UIKit projects that still use storyboards/XIBs. This includes:
- Legacy codebases (significant installed base)
- Capacitor/Cordova hybrid apps (which ship with storyboards by default)
- Enterprise apps with long maintenance cycles
- Teams gradually migrating from UIKit to SwiftUI
- SwiftUI adoption is growing, but storyboards remain common in production apps as of 2026. The feature has a narrowing but still substantial audience.
- Input files are valid IB XML documents (the script should fail gracefully on malformed input).
Suggested Implementation
Class Structure
Follow the existing VisualDiffer pattern from visual_diff.py:
class StoryboardDiffer:
"""Performs structured comparison between Interface Builder documents."""
def __init__(self):
pass
def parse(self, file_path: str) -> dict:
"""Parse a .storyboard or .xib into a semantic model."""
# Extract: scenes, viewControllers, views (recursive),
# constraints, outlets, actions, segues, dependencies
pass
def diff(self, baseline_path: str, current_path: str) -> dict:
"""Compare two IB documents and return structured changes."""
pass
Semantic Model
The parser should extract these elements from the IB XML:
| Element |
XML Path |
Key Attributes |
| View Controllers |
//scene/objects/viewController |
id, customClass, storyboardIdentifier |
| Views |
//view (recursive) |
id, contentMode, translatesAutoresizingMaskIntoConstraints |
| Constraints |
//constraint |
id, firstItem, firstAttribute, secondItem, constant |
| Outlets |
//outlet |
property, destination, id |
| Actions |
//action |
selector, destination, eventType |
| Segues |
//segue |
id, destination, kind, identifier |
| Dependencies |
//plugIn, //deployment |
identifier, version |
CLI Interface
# Compare two files
python scripts/storyboard_diff.py baseline.storyboard current.storyboard
# With options
python scripts/storyboard_diff.py old.xib new.xib --json --verbose
Flags: --json, --verbose, --ignore-ids (ignore internal IB object IDs which change on re-save).
Progressive Disclosure
Default output (3-5 lines, ~10 tokens):
Storyboard diff: 3 changes
Added: UIButton in LoginViewController
Changed: constraint constant 16→24 on nameLabel
Removed: segue "showDetail" from MainViewController
Verbose output (full details):
Storyboard diff: baseline.storyboard → current.storyboard
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Scene: LoginViewController (BYZ-38-t0r)
+ View: UIButton (xkd-29-f3k)
title: "Submit"
frame: (20, 400, 335, 44)
~ Constraint: (abc-12-def)
attribute: leading
constant: 16 → 24
Scene: MainViewController (tne-QT-ifu)
- Segue: "showDetail" (seg-45-xyz)
kind: show
destination: DetailViewController
Dependencies: no changes
JSON output:
{
"baseline": "baseline.storyboard",
"current": "current.storyboard",
"summary": {"added": 1, "changed": 1, "removed": 1},
"changes": [
{"type": "added", "element": "view", "scene": "LoginViewController", "class": "UIButton", "id": "xkd-29-f3k"},
{"type": "changed", "element": "constraint", "id": "abc-12-def", "attribute": "constant", "from": 16, "to": 24},
{"type": "removed", "element": "segue", "scene": "MainViewController", "identifier": "showDetail", "id": "seg-45-xyz"}
]
}
Shared Utilities
No new common modules needed — xml.etree.ElementTree from stdlib handles all XML parsing. The script is self-contained.
Exit Codes
0: Files are identical or diff completed successfully with no changes
1: Differences found (useful for CI gating)
2: Error (file not found, malformed XML)
Open Questions
-
SwiftUI relevance: With SwiftUI adoption accelerating, is the user base for this feature large enough to justify inclusion? Storyboards are still common in production (especially hybrid apps via Capacitor/Cordova, enterprise apps, and legacy codebases), but the trend is away from IB.
-
ID stability: IB regenerates internal object IDs (BYZ-38-t0r etc.) unpredictably. The --ignore-ids flag helps, but should we default to ignoring IDs and instead match elements by structural position + class?
-
Constraint diffing depth: Auto Layout constraints reference objects by ID. Should we resolve these to human-readable names (e.g., "leading of submitButton to superview") or keep raw IDs?
-
ibtool integration: xcrun ibtool --export can extract properties from IB documents in plist format. Should we use this as a preprocessing step for richer data, or is raw XML parsing sufficient? ibtool adds a subprocess dependency but provides validated data.
-
Scope: Should this also support .nib (compiled) files, or only source formats (.storyboard, .xib)?
-
Single-file inspection: Should the script also support a single-file mode (python scripts/storyboard_diff.py inspect Main.storyboard) that dumps the semantic model without comparison? This would be useful for AI agents exploring unfamiliar projects.
References
- ibtool man page — Apple's IB compiler/inspector tool
- Interface Builder XML format — Document type
com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB
- xml.etree.ElementTree — Python stdlib XML parser
- Existing pattern:
visual_diff.py in this repo for comparison-style script design
Intro
Add a
storyboard_diff.pyscript that performs structured diffing of Interface Builder.storyboardand.xibfiles. Rather than treating these as opaque XML blobs (which produce noisy, unreadable git diffs), the script would parse the XML into semantic components — view controllers, views, constraints, outlets, actions, segues — and report meaningful changes in a token-optimised format.Value Proposition
Added: UIButton "Submit" in LoginViewController,Changed: constraint leading 16→24 on nameLabel.visual_diff.py— one catches pixel-level changes, the other catches structural changes.Prerequisites
xml.etree.ElementTreefrom Python stdlib (no external dependencies needed)ibtoolis available viaxcrun ibtoolbut has limited diff capabilities — it supports--exportfor property extraction and--previous-file/--localize-incrementalfor localization deltas, but no general-purpose structural diff. Our script fills this gap.Assumptions
Suggested Implementation
Class Structure
Follow the existing
VisualDifferpattern fromvisual_diff.py:Semantic Model
The parser should extract these elements from the IB XML:
//scene/objects/viewControllerid,customClass,storyboardIdentifier//view(recursive)id,contentMode,translatesAutoresizingMaskIntoConstraints//constraintid,firstItem,firstAttribute,secondItem,constant//outletproperty,destination,id//actionselector,destination,eventType//segueid,destination,kind,identifier//plugIn,//deploymentidentifier,versionCLI Interface
Flags:
--json,--verbose,--ignore-ids(ignore internal IB object IDs which change on re-save).Progressive Disclosure
Default output (3-5 lines, ~10 tokens):
Verbose output (full details):
JSON output:
{ "baseline": "baseline.storyboard", "current": "current.storyboard", "summary": {"added": 1, "changed": 1, "removed": 1}, "changes": [ {"type": "added", "element": "view", "scene": "LoginViewController", "class": "UIButton", "id": "xkd-29-f3k"}, {"type": "changed", "element": "constraint", "id": "abc-12-def", "attribute": "constant", "from": 16, "to": 24}, {"type": "removed", "element": "segue", "scene": "MainViewController", "identifier": "showDetail", "id": "seg-45-xyz"} ] }Shared Utilities
No new common modules needed —
xml.etree.ElementTreefrom stdlib handles all XML parsing. The script is self-contained.Exit Codes
0: Files are identical or diff completed successfully with no changes1: Differences found (useful for CI gating)2: Error (file not found, malformed XML)Open Questions
SwiftUI relevance: With SwiftUI adoption accelerating, is the user base for this feature large enough to justify inclusion? Storyboards are still common in production (especially hybrid apps via Capacitor/Cordova, enterprise apps, and legacy codebases), but the trend is away from IB.
ID stability: IB regenerates internal object IDs (
BYZ-38-t0retc.) unpredictably. The--ignore-idsflag helps, but should we default to ignoring IDs and instead match elements by structural position + class?Constraint diffing depth: Auto Layout constraints reference objects by ID. Should we resolve these to human-readable names (e.g., "leading of submitButton to superview") or keep raw IDs?
ibtool integration:
xcrun ibtool --exportcan extract properties from IB documents in plist format. Should we use this as a preprocessing step for richer data, or is raw XML parsing sufficient? ibtool adds a subprocess dependency but provides validated data.Scope: Should this also support
.nib(compiled) files, or only source formats (.storyboard,.xib)?Single-file inspection: Should the script also support a single-file mode (
python scripts/storyboard_diff.py inspect Main.storyboard) that dumps the semantic model without comparison? This would be useful for AI agents exploring unfamiliar projects.References
com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIBvisual_diff.pyin this repo for comparison-style script design