macOS 26 (Tahoe) ONLY. This is non-negotiable.
- No iOS, iPadOS, visionOS, watchOS, tvOS
- No backwards compatibility (no macOS 15, 14, etc.)
- No
#availablechecks or@availableattributes - No multi-platform conditionals
DocumentGroupLaunchSceneDocumentLaunchViewUIKitanything- Any API marked "iOS only" or "iPadOS only" in documentation
AppKit/NSApplication/NSWindowfor macOS-specific needsSwiftUIwith macOS idioms (Window scenes, Settings scenes, NSOpenPanel)- Swift 6.2 concurrency (MainActor isolation by default)
- TCA 1.23.1 modern patterns:
@ObservableState,StoreOf,@Bindable(noWithViewStore) - On macOS 26, Observation is native; do not use
WithPerceptionTrackingunless targeting older OSes.
Reverse-engineer and clone Reality Composer Pro's functionality:
- Open
.realitycomposerpropackage files - Parse and display project structure
- Edit scenes and assets
- Save changes back to the package format
This project has a split identity:
- Public open-source repo (
Deconstructed) — anyone should be able to clone and build - Separate private workflow repo (
USDTools) — may exist locally for internal work, but is not required for the public Deconstructed build path
The root Package.swift and inner Packages/DeconstructedLibrary/Package.swift declare remote URLs for CI/public consumption. But locally, Xcode resolves dependencies at the workspace level, overriding what Package.swift says.
All dependency resolution happens through Deconstructed.xcworkspace. The workspace includes local package references that Xcode prefers over remote URLs with the same identity.
This means:
Package.swiftremote URLs are fallbacks for CI / clean clones only- The inner
DeconstructedLibrary/Package.swiftmay referencebranch: "main"or pinned revisions — it doesn't matter locally because the workspace overrides them - Editing local first-party package checkouts such as
/Volumes/Plutonian/_Developer/USDInteropcompiles immediately when the workspace is configured to use them - You must open
Deconstructed.xcworkspace, not the.xcodeproj
Running swift build inside Packages/DeconstructedLibrary/ will fail because:
- SwiftPM resolves deps from
Package.swiftdirectly (no workspace override) - The inner package may point to
branch: "main"while root pins a revision — SwiftPM cannot reconcile two different revision-based requirements for the same package - Local-only packages (like
SelectionOutlineas a sibling) resolve fine, but remote deps conflict
Always build through Xcode workspace or from the root Package.swift:
# From repo root — uses root Package.swift which is kept CI-safe
swift build --target ViewportUI- The root
Package.swiftis the source of truth for public/CI builds - It pins stable versions of remote deps (revisions or semver)
- Local packages (
SelectionOutline, etc.) use relative paths that work from root - Never edit the inner
DeconstructedLibrary/Package.swiftdependency URLs to match root — they serve different purposes
The current split is:
- public package family:
USDInterop,USDInterfaces,USDInteropCxx,USDOperations - private workflow/value layer:
USDTools
Rules:
- generic scene operations belong in
USDOperations - workflows, diagnostics, repair, packaging, conversion, and heuristics do not
- do not reintroduce dependencies on
USDToolsor legacy advanced modules into the public Deconstructed build path
Analyze this real RCP project for format details:
/Volumes/Plutonian/_Developer/Deconstructed/references/Base
IMPORTANT: The document is the .realitycomposerpro bundle, NOT the parent folder. RCP creates an SPM package wrapper around it for Xcode/Swift integration.
Base/ # SPM package (wrapper for integration)
├── Package.swift # SPM manifest
├── Package.realitycomposerpro/ # <- THE DOCUMENT (what we open/save)
│ ├── ProjectData/
│ │ └── main.json # UUID mappings (paths reference ../Sources/)
│ ├── WorkspaceData/
│ │ ├── Settings.rcprojectdata # Editor settings (JSON)
│ │ ├── SceneMetadataList.json # Hierarchy state
│ │ └── <username>.rcuserdata # Per-user prefs
│ ├── Library/
│ └── PluginData/
└── Sources/ # Assets (sibling to document)
└── <ProjectName>/
├── <ProjectName>.swift # Bundle accessor
└── <ProjectName>.rkassets/
└── Scene.usda # USD scene files
- Double-clicking
.realitycomposerproopens RCP - RCP does NOT display the parent folder structure
- Asset paths in
main.jsonlike/Base/Sources/Base/Base.rkassets/Scene.usdanavigate relative to SPM root
main.json contains pathsToIds mapping scene file paths to UUIDs. However, RCP treats this as a loose index, not a strict manifest:
- Stale entries persist: Deleted/renamed files keep their old entries
- Duplicates exist: Same file may appear with different path formats and different UUIDs
- Path formats are inconsistent: Mix of
/Project/Sources/...,Project/Sources/...,/Sources/... - Percent-encoding varies: Some paths have
%20for spaces, others don't
Example from a real project:
{
"/MyProject/Sources/MyProject/MyProject.rkassets/Scene.usda": "UUID-1",
"MyProject/Sources/MyProject/MyProject.rkassets/Scene.usda": "UUID-2",
"/Sources/MyProject/MyProject.rkassets/Deleted.usda": "UUID-3"
}Filesystem is the source of truth for what exists. main.json is a best-effort UUID lookup.
- Scan
Sources/directory on disk to find.rkassets - Recursively enumerate actual files/folders
- Consult
main.jsononly for UUID assignment (optional, gracefully handles missing)
// Find .rkassets by scanning disk, not parsing main.json paths
private func findRKAssets(in sourcesURL: URL) -> URL? {
// Scan Sources/<ProjectName>/<ProjectName>.rkassets
}
// UUID lookup is best-effort
private func loadSceneUUIDLookup(documentURL: URL) -> [String: String] {
// Returns empty dict if main.json unreadable - discovery still works
}When moving/renaming files:
- Perform filesystem operation first (
FileManager.moveItem) - Update
main.jsonpath mappings to match new locations - Re-scan filesystem to rebuild asset tree
This ensures the UI always reflects actual disk state, even if main.json gets out of sync.
Inferred reasons (not from Apple docs):
- Undo/history support: Old UUIDs preserved for potential restoration
- Reference stability: External links to scenes by UUID survive renames
- Collaboration: Merging projects with different rename histories
- Lazy cleanup: No benefit to aggressive pruning
- Never trust
main.jsonfor file existence - always verify on disk - Generate stable IDs from paths -
AssetItem.stableID(for:)uses MD5 of path - Handle missing UUIDs gracefully - new files may not be in
main.jsonyet - Update
main.jsonon moves/renames - keep it roughly in sync for RCP compatibility - Don't prune stale entries - match RCP's behavior for interoperability
AssetItem.Equatable must compare more than just id:
public static func == (lhs: AssetItem, rhs: AssetItem) -> Bool {
lhs.id == rhs.id
&& lhs.name == rhs.name
&& lhs.children == rhs.children // Critical for tree updates
}Since stableID is path-based, a folder's ID stays constant even when children change. Without comparing children, SwiftUI/TCA won't detect tree structure changes after file moves.
| Component | Choice |
|---|---|
| UI Framework | SwiftUI (macOS 26) |
| App Architecture | Document-based (DocumentGroup, FileDocument) |
| Document Type | .realitycomposerpro bundle via FileWrapper |
| UTType | com.apple.realitycomposerpro (imported) |
| Swift Version | 6.2 with strict concurrency |
| Min Deployment | macOS 26.0 only |
| File | Purpose |
|---|---|
DeconstructedApp.swift |
App entry point, scene declarations |
Packages/DeconstructedLibrary/Sources/RCPDocument/DeconstructedDocument.swift |
FileDocument conformance for packages |
Packages/DeconstructedLibrary/Sources/DeconstructedModels/ProjectModels.swift |
Codable structs matching RCP JSON schemas |
Packages/DeconstructedLibrary/Sources/DeconstructedUI/ContentView.swift |
Main document editing interface |
Packages/DeconstructedLibrary/Sources/DeconstructedFeatures/LaunchExperience.swift |
Welcome window UI + recent projects |
Packages/DeconstructedLibrary/Sources/DeconstructedClients/NewProjectCreator.swift |
New project scaffolding + open workflow |
- Check if the API exists on macOS (not just iOS)
- Use the latest macOS 26 APIs without guards
- Reference the actual RCP package structure above
- Keep the document-based architecture intact