[codex] Preserve nested Swift specializations#76
Conversation
Single-image Export still requires entering an image first; bulk extraction across many frameworks was repetitive. Add a parallel File → Export Multiple Images… wizard that picks N images up front, then runs the per-image export concurrently (up to 8 in parallel) into destinationURL/<imageName>/ subdirectories via the unchanged single-image RuntimeEngine.exportInterfaces API. Progress is shown as an NSTableView of per-image rows (queued / running with progress bar / succeeded / failed). The wizard container (ExportingViewController + ExportingRoute) is reused; the single-image entry remains untouched.
Folds the few remaining AppKitViewController<VM> subclasses into
UXKitViewController<VM> and gives the base an overridable contentInsets
so sheet/popover screens can inset the rooted contentView without
hand-rolling padding constraints. Roots that previously called
hierarchy {} on self now drive contentView.hierarchy {} instead.
Also restacks the batch-export image-selection cell into nested
HStack/VStack with a per-row path subtitle, switching the table to
usesAutomaticRowHeights for the taller two-line rows.
Drops the per-image objects probe that gated the ObjC/Swift format choices behind whether a selected image actually exposed each runtime. The probe ran in the configuration step's init before the user had picked any images, so the radios stayed hidden on entry. Both format groups are now always visible; objectsByImage/loading state on BatchExportingState is no longer needed and goes away with it.
…lved Flip UIFoundation's local checkout to `isEnabled: true` so the workspace build no longer toggles between local and SPM-resolved copies depending on the per-machine `usingLocalDependencies` flag; bump both Package.resolved files to match the resulting checkout graph.
Every server-bound command used to be declared three times — once in the public API's local/remote split, once in setupMessageHandlerForServer, and once in RuntimeEngineProxyServer's handler registry. Adding a new command meant editing all three call sites, and missing the proxy registration silently broke remote mirroring. Collapse the trio into a single RuntimeEngineRequest protocol whose conformers describe the wire name and own a perform(on:) local implementation. RuntimeEngine.dispatch(_:) routes through the same conformer for both client (sendMessage) and local arms, and a single registerSharedHandlers(on:engine:) call wires every shared command on both the server arm and the proxy server. engineList stays server-only (no proxy equivalent), iconRequest stays proxy-only, and the objectsInImage progress-pumping variant remains a server-side override. Adding a new command is now: declare an XxxRequest conformer + append one register(...) line — both server entry points pick it up automatically.
…legate Both BatchExportingImageSelectionViewController and SpecializationViewController previously blocked the table/outline selection by hooking shouldSelectItem in an NSOutlineViewDelegate. Setting selectionHighlightStyle = .none achieves the same visual result without the delegate plumbing, so retire the delegate extensions. SpecializationViewController also drops the now-redundant rx.setDelegate(self) wiring; the image-selection sheet gains a small inset around its content stack while we are touching the layout.
The completion step used to render a single concatenated multi-line summary string and a centered checkmark hero. Swap that for a horizontally laid-out header (compact icon + title + destination subtitle) and four stat cards (Interfaces, Images, ObjC·Swift, Duration) driven by a typed Summary struct on the ViewModel. The ViewModel now formats and partitions the numbers up front (failure ratios, locale-aware integer grouping, tilde-abbreviated destination path) so the view stays a thin renderer.
…s gotcha Codify the project rule that any custom AppKit view needing layer-level visuals (corner radius, border, background, shadow) MUST inherit UIFoundationAppKit.LayerBackedView rather than hand-rolling wantsLayer + layer?.xxx. Call out the borderPositions = [] default that silently swallows non-zero borderWidth + non-nil borderColor so future contributors don't lose an afternoon to it.
Replace the SwiftUI @AppStorage-backed includeMetadata flag with the project @userdefault wrapper and persist the ObjC/Swift format choices as well, so export layout preferences survive across sessions for both single and batch export. Make Format Codable so @userdefault can store it, and centralize the defaults keys in ExportingDefaultsKey.
Adopt the current .repeat(.periodic) symbol-effect API and convey activity with a rotating SF Symbol: restore the ImageView wrapper for the indexing row icon and spin a refresh glyph while a batch export row is running.
…lView Drop the hand-rolled wantsLayer + layer?.backgroundColor handling in favor of LayerBackedTableCellView's backgroundColor property, matching the project's layer-backed view conventions.
Fold the two-stage filtered/selection combineLatest into a single map so cell view models are built in one pass, reading selection state directly from exportingState.
MachInjector 0.2.0 → 0.3.0, RunningApplicationKit 0.3.2 → 0.3.3, SwiftyXPC 0.5.100 → 0.5.102. Point swift-mobile-gestalt and swift-navigation at MxIris-Library-Forks (0.5.0 / 2.8.100) for the in-progress patches the upstream repos don't carry. Pick up swift-helper-service 0.1.3 and swift-identified-collections 1.1.1 (transitive from the helper service); drop NSAttributedStringBuilder which no longer has any callers.
The server-only progress-bearing override for runtimeObjectsInImage was declaring its payload as a bare String, but every caller — dispatch(ObjectsInImageRequest:), _remoteObjectsWithProgress, and the shared handler this override replaces — sends the structured ObjectsInImageRequest envelope. The mismatch failed every objects(in:) call against a remote engine with NSCocoaErrorDomain 4864. Send ObjectsInImageRequest from _remoteObjectsWithProgress and decode it the same way on the server arm so both objects(in:) and objectsWithProgress(in:) hit a symmetric wire form. Also drop the unused `try` on IsImageIndexedRequest.perform — its underlying _isImageIndexed is non-throwing.
C++ template type names routinely produce display names longer than the 255-byte NAME_MAX on APFS/HFS+, which made the export pipeline throw NSFileWriteInvalidFileNameError (Cocoa 642) for fully-spelled std::unordered_map<…> and friends. Clamp every export file name to 255 UTF-8 bytes, truncating the base on a Character boundary so the result stays valid UTF-8 and never splits a multi-byte scalar. Append an FNV-1a-based ~8-hex disambiguator computed over the *full* base name so two distinct long names that share a truncated prefix don't collide on disk, and the same object always maps to the same file name across runs (Hashable.hashValue is per-run seeded and unsuitable). The suffix (extension / category tag) is never dropped.
A "succeeded" image whose object count silently included a non-zero failure tally was indistinguishable from a fully clean export — the row showed the green checkmark and only the small "N failed" detail tag. Capture .objectFailed events from RuntimeInterfaceExportReporter into a new BatchExportingObjectFailure list, thread it through the per-image outcome, and render partial failures distinctly: - progress + completion rows switch to an orange triangle icon and an orange detail color when failed > 0 - both views attach a multi-line tooltip listing each failed object and its error (capped at 50 entries with "…and N more" overflow) Also pre-flight the image with isImageLoaded / loadImage before exporting so an unloaded image fails the row up front with a real error description instead of silently producing zero objects.
Replace the single-shot Back toolbar item with a Previous/Next navigation group whose visibility, enablement, and target object are derived from `DocumentState.selectionStack` and a new `selectionIndex` cursor. Sidebar clicks now `.push` onto the history instead of replacing the root, so the user can walk backward and forward through every object they've inspected (browser-style). New `.backward`/`.forward` routes move the cursor without mutating the array; `.push` from mid-history truncates the abandoned forward branch first; `.pop` keeps its prior removeLast semantics for callers that need to shrink the history. Content/Inspector coordinators now read `selectedRuntimeObject` (stack[cursor]) instead of `stack.last`, and SidebarRuntimeObjectList's visual selection follows the cursor so previous/next also re-highlight the matching sidebar row when the object lives in the current image.
The cell view is no longer Sidebar-specific — moving it into Base keeps it reachable from non-sidebar surfaces (Open Quickly, popovers) without crossing module folders.
Replace ad-hoc `.centerY` / `.left` / `.vStackCenter` alignments with the standard `.center` / `.leading` set so the call sites match the axis-aware naming the stack view wrappers expose elsewhere.
…encies Hoist the usingLocalDependencies flag into the LocalSearchPath.package default so every local checkout no longer has to repeat isEnabled: usingLocalDependencies, and uncomment the secondary RxAppKit local path now that the default applies.
…iews Replace the isReorderable-driven ternary between rx.reorderableNodes and rx.nodes with the new rx.nodes(options:) overload, keeping the binding site uniform regardless of whether reordering is enabled.
- Bump MachOSwiftSection exact pin to 0.12.0-beta.2 (specialization fixes) - Refresh RuntimeViewerCore / RuntimeViewerPackages Package.resolved against new dependency tags (RxAppKit 0.4.0, MachOObjCSection 0.7.102, FrameworkToolbox 0.7.1, Semaphore 0.1.1, swift-demangling 0.4.1, swift-dyld-private 1.2.1, swift-objc-dump 0.8.101). - Refresh workspace-level Package.resolved for both Debug and Distribution workspaces so CI builds against the same pins as the local Xcode session. - Add Changelogs/v2.1.0-beta.2.md.
Wrap `SelectionRouter` and `EmptyRouteTransitionContext` in DocumentState behind `#if os(macOS)` since they conform to CocoaCoordinator's `TransitionContext` (which XCoordinator does not provide on iOS), and gate the three remaining cross-platform call sites — InspectorRelationships, ContentText, Sidebar — that still triggered `documentState.selectionRouter` without a guard. Wrap `InspectorSwiftSpecializationViewModel` in `#if os(macOS)` because it addresses `InspectorRuntimeObjectRoute.requestSpecializationSheet`, which only exists in the macOS branch of that route enum. Guard the `settings.general.sidebarMaxExpansionDepth` lookup in SidebarRootViewModel behind `#if canImport(RuntimeViewerSettings)` — the Settings target is macOS-only, so iOS falls back to `.max`.
…form `SelectionRouter` no longer conforms to the coordinator framework's `Router` protocol — its purpose is to mutate `DocumentState` and emit a route, not perform a UI transition, so the coordinator machinery (and the `TransitionContext` / `TransitionProtocol` divergence between CocoaCoordinator and XCoordinator) was buying nothing. It's now a plain `@MainActor` class with a public `trigger(_:)`. Drop `EmptyRouteTransitionContext` accordingly. `RuntimeViewerSettings` was already platform-agnostic in its sources (only imports Foundation/Observation/MetaCodable/RuntimeViewerCore), so the `condition: .when(platforms: appkitPlatforms)` on the `RuntimeViewerApplication` dependency was unnecessarily fencing iOS off from settings access. Make it an unconditional dependency and drop the matching `#if canImport(RuntimeViewerSettings)` guards in `ViewModel`. Drop the `#if os(macOS)` around `InspectorRuntimeObjectRoute` — every case references only `RuntimeObject` (cross-platform) so the iOS `typealias InspectorRuntimeObjectRoute = InspectorRoute` workaround is no longer necessary. Revert the temporary platform guards added in 5335d1a around `documentState.selectionRouter`, `settings.general.sidebarMaxExpansionDepth`, and the whole `InspectorSwiftSpecializationViewModel` — they all build on iOS now.
…h iOS XCoordinator parity Revert the standalone `SelectionRouter` class introduced in 6ac542f back to a real `Router` conformance — macOS keeps its CocoaCoordinator-based implementation untouched, and iOS gets a parallel XCoordinator-based one using the equivalent types: `Router<RouteType>` instead of `Router<Route>`, and `EmptyRouteTransitionContext: TransitionProtocol` in place of `TransitionContext` (XCoordinator merges the `TransitionContext.presentables` surface into `TransitionProtocol`). XCoordinator's `Router` extends `Presentable`, so the iOS branch adds a deliberately no-op `viewController`/`router(for:)` surface — this router mutates state and emits a route, it never actually presents anything. Public surface is unchanged: callers see `any Router<SelectionRoute>`, so `documentState.selectionRouter.trigger(.push(x))` works the same on both platforms.
0fdca62 already eliminated the @Mutex `_modify` coroutine accessor that bec9f4b's `processReceivedData` snapshot and `RuntimeNetworkConnection` AsyncStream consumer were guarding against. With the real trigger gone the guards are dead weight, and the AsyncStream consumer's strictly serial drain was incidentally compounding latency when a peer echoed handler errors at high rate. The v2.1.0-beta.4 sim-side image-loading regression turned out not to be a channel-layer issue — it was a background-indexing handler echoing `DyldOpenError` for the peer's own main executable, triggering a peer-error ping-pong on `sendSemaphore`. That requires a separate fix on the indexing handler; see v2.1.0-beta.4 changelog known-issue note.
…indexing If the remote engine serving as the Server side has background indexing on, the indexer's `LoadImageForBackgroundIndexingRequest` handler raises `DyldOpenError` on the peer's own main exec, the error response carries no `identifier`, the peer envelope-decodes it as `keyNotFound`, and both sides ping-pong on the shared `sendSemaphore` — image-list pushes and progress events starve out. Workaround: disable background indexing on the remote-Server side. Two-part fix targeted at the next build.
The old sendRequest held sendSemaphore from wait-to-resume, so a slow peer handler (e.g. 20 s of section parsing during background indexing) monopolized the channel and blocked every other send-in-flight at the local outbox. Stamp a UUID nonce per round trip, key pendingRequests by nonce, and release the semaphore as soon as the write returns; concurrent sendRequests now wait on responses in parallel and identifier-keyed collisions on the routing table are impossible. Adjacent fix: the receive catch arm was echoing a bare RuntimeNetworkRequestError carrying no identifier when envelope decode failed, which caused the peer to also fail decode and echo back — both sides ping-ponging on the same semaphore and starving every other request. Split into two arms: decode failures swallow silently; handler failures echo via the same nonce-keyed envelope used by successful responses, so the peer's sendRequest routes the error back to its matching pending entry instead of triggering a re-echo loop. Wire format is backward compatible: nonce is optional, missing values fall back to identifier-keyed routing. See Changelogs/v2.1.0-beta.4.md "Remote-Server background indexing stalls image loading" for the failure mode this addresses.
Two changes that together let background indexing work on non-local (XPC / Bonjour / iOS Simulator) engines. 1. _loadImageForBackgroundIndexing now skips dlopen when imageList already contains the canonical path. The BFS routinely visits the peer's own main executable, which dlopen refuses to re-open by definition; on iOS Simulator it also hits system images whose canonical path differs from dyld's runtime form due to DYLD_ROOT_PATH rewriting, resolving to (no such file) on disk. The previous-commit transport hardening makes the spurious DyldOpenError no longer fatal, but guarding at source is cheaper. 2. canOpenImage / rpaths / dependencies now dispatch instead of reading the client process's dyld directly. The protocol's "Pure local check" assumption only ever worked on the local engine; remote engines were silently returning empty dependency lists from the client-side DyldUtilities.machOImage lookup, so BFS stuck at the root and batches only ever indexed the main executable instead of the full dependency closure. Wire-format addition: RuntimeDependencyEntry struct replaces the tuple-typed dependencies return value at the dispatch seam (tuples aren't Codable). The manager-facing API still uses tuples and needs no changes.
…ustom modes
Replace the single `BackgroundMode` toggle with a three-level switch:
- `isEnabled` (master) — overall on/off; when off, neither sub-mode runs.
- `heuristic.isEnabled` — main-executable BFS at document open / engine swap.
- `custom.isEnabled` — always-index list (entries moved into `custom.entries`).
`maxConcurrency` is now shared between sub-modes; `depth` lives under
`heuristic`. The coordinator reconciles each toggle independently in
`handleSettingsChange` and uses the new `cancelBatches(matching:)` Manager
API to scope cancellation to one sub-mode without disturbing the other.
Drop the dyld add-image pump entirely — heuristic discovery now relies
solely on document-open / fullReload triggers, matching the new sub-mode's
documented semantics ("images dlopen'd after the initial sweep are not
auto-indexed").
Popover groups batches by `RuntimeIndexingBatchReason.Category` so
HEURISTIC / ALWAYS INDEX / MANUAL each get their own collapsible header.
Always-index groups flatten one level (entry → item) so single-image
entries render as a flat list instead of an empty intermediate batch row.
`imageNodes` is now a `nonisolated` getter backed by the underlying `CurrentValueSubject`, with all mutation routed through the existing actor-isolated `setImageNodes(_:)`. Callers no longer need an `await` hop just to read the current snapshot. Use this to: - Synchronously gate the "Export Multiple Images" menu item in `MainWindowController.validateMenuItem` on `imageNodes.count >= 2` — `responds(to:)` runs on the main thread and can't suspend. - Drop the redundant `await` in `BatchExportingCoordinator.loadAvailableImages`. - Drop the redundant `await` in `RuntimeEngineProxyServer.sendInitialData`.
- `RuntimeMessageChannel`: discard the `yield` result explicitly so the compiler doesn't warn about an unused `Discarding` value. - `RuntimeViewerPackages/Package.swift`: comment out the local-path branches for `UXKitCoordinator` and `OpenUXKit` so the repo resolves against the OpenUXKit remote unconditionally. The local checkouts those pointed at no longer exist for most contributors.
Both bumps are semver-compatible with the existing `from:` pins and synchronize the declared floor with the latest published tags: - UIFoundation 0.10.2 adds an opt-out for the visual-effect host on `UXKitViewController` and respects safeAreaLayoutGuide on macOS 11+. - RxAppKit 0.5.0 drives `rx.itemSelected` (NSTableView / NSOutlineView) off `selectionDidChangeNotification` so keyboard / type-select selection events surface alongside clicks — already relied on by the type-select navigation debounce shipped in 28fd4c4.
1.13.0 declares the trait set (Clocks / CombineSchedulers / Foundation / FoundationNetworking) the resolved graph enables; 1.12.0 doesn't, so release-channel resolves under USING_LOCAL_DEPENDENCIES=0 fail with "declares no traits". Catches up the main-branch pin with the floor that already shipped on the v2.1.0-beta.4 release tag (0e7b9f2).
- Add Changelogs/v2.1.0-beta.5.md. Clears beta.4's known-issue ping-pong on remote-Server background indexing (b276703 + 1d9becf), re-rolls the Always Index list that beta.4 had to revert (453644c stayed on main), and reorganizes Background Indexing settings into master + heuristic + custom. Includes the imageNodes nonisolated refactor + UIFoundation / RxAppKit / swift-dependencies pin catch-up.
This reverts commit a921e4c.
Introduce a `@MainActor`-isolated `CellViewModel` base in RuntimeViewerApplication and rebase the three batch-export row/cell view models on it, replacing the per-class `NSObject, @unchecked Sendable` boilerplate. `BatchExportingImageSelectionCellViewModel`'s `Differentiable` conformance is now declared `@MainActor` to match. Also refresh Package.resolved across the workspace and per-package manifests to pick up new dependency revisions.
….plist - `ITSAppUsesNonExemptEncryption=false` skips the per-build App Store Connect export-compliance prompt. - `NSLocalNetworkUsageDescription` explains the optional Bonjour mirror feature; required by iOS before `_runtimeviewer._tcp` discovery can prompt for local-network access.
RuntimeEngine no longer caches an XPC listener endpoint or downcasts its connection to a transport-specific protocol. Announcing the listener endpoint to the Mach Service is now self-contained in RuntimeXPCServerConnection — the only place that already knows it is an XPC server with an endpoint to publish — so neither the engine nor the injected RuntimeViewerServer entry point needs to mention XPC. Collapse the ad-hoc bonjourEndpoint: / xpcServerEndpoint: parameters of connect(...) into a single credential: RuntimeConnectionCredential, so session-scoped endpoints stop being passed around as untyped (any Sendable) and the call sites describe their intent (.bonjour / .xpcServer) directly.
There was a problem hiding this comment.
Code Review
This pull request introduces an identityPath property to RuntimeObject and RuntimeObjectKey to uniquely identify nested types under different tree paths, preventing duplicate entries and preserving nested specializations in the sidebar. It also updates layout alignments in several AppKit view controllers and adds corresponding unit tests. Feedback on these changes highlights two issues: first, the parentIdentityPath is not passed to makeRuntimeObject within the specialize method, resulting in flat identity paths for dynamically specialized nested types; second, the newly introduced specializedDefinitionByObject cache is never cleared when the index configuration is updated, which could lead to memory leaks or stale lookups.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| let specializedDefinition = try await baseTypeDefinition.specialize( | ||
| with: result, | ||
| typeArgumentNodes: typeArgumentNodes.count == upstreamRequest.parameters.count ? typeArgumentNodes : nil, | ||
| derivingNestedSpecializationsWith: specializer, | ||
| selection: upstreamSelection, | ||
| typeArgumentNodesByParameter: resolved.nodesByParameter, | ||
| in: machO | ||
| ) |
There was a problem hiding this comment.
In specialize(for:with:), when makeRuntimeObject is called to create the specialized RuntimeObject (around line 663), the parentIdentityPath parameter is not provided. This causes the newly created specialized object to have a flat identityPath (just its own mangled name) instead of being nested under the parent's identityPath (object.identityPath).
Since RuntimeObjectKey uses identityPath to distinguish same-named objects under different tree paths, failing to pass parentIdentityPath: object.identityPath breaks the identity of dynamically specialized nested types and can lead to lookup mismatches or duplicate entries in the sidebar.
Please update the makeRuntimeObject call to pass parentIdentityPath: object.identityPath.
| // both maps and throw `invalidRuntimeObject`. | ||
| private var interfaceByObject: OrderedDictionary<RuntimeObjectKey, RuntimeObjectInterface> = [:] | ||
|
|
||
| private var specializedDefinitionByObject: [RuntimeObjectKey: TypeDefinition] = [:] |
There was a problem hiding this comment.
The new specializedDefinitionByObject cache is populated when specialized types are created, but it is never cleared when the index is re-prepared or updated.
In updateConfiguration(using:transformer:), when newIndexConfiguration.showCImportedTypes != oldIndexConfiguration.showCImportedTypes, interfaceDefinitionNameByObject is cleared, but specializedDefinitionByObject is not. This can lead to a memory leak of stale TypeDefinition objects and potential lookup mismatches. Please ensure specializedDefinitionByObject.removeAll() is called whenever interfaceDefinitionNameByObject is cleared.
…isonStep<Self> form
The `ComparableDefinition<Self> { makeComparable { … } }` form requires a
newer FrameworkToolbox than batch-export currently pins, and is unrelated
to the nested-specialization fix that follows. Roll the three call sites
back to the `some ComparisonStep<Self>` form already supported in the
pinned dependency set so the bugfix branch can build against
batch-export's deps without forcing an unrelated SPM bump.
… cell viewmodel The preceding two commits introduced an identityPath String on every RuntimeObject to disambiguate the same Swift metadata appearing under different sidebar paths (e.g. EventListenerPhase.Value<Event> reached via the inner generic vs. via Phase<Event>). That coupling makes the data-layer model carry a UI concern and costs ~30-80MB on large indexes where N RuntimeObject instances each pay for a per-node path String. Drop identityPath from RuntimeObject and RuntimeObjectKey so the latter returns to its metadata-identity role. Recreate the disambiguation on SidebarRuntimeObjectCellViewModel.StableID by folding a recursive parent fingerprint into the cell's own (imagePath, name, kind). The fingerprint is not cached: stableID reads it on demand so late-binding parent changes show up automatically and runtimeObject.children is excluded so splicing a child does not flip the parent's fingerprint. The regression scenario still holds — EventListenerPhase.Value<Event> survives an ancestor EventListenerPhase<PanEvent> specialization, as covered by the existing ancestorSpecializationPreservesExistingNestedSpecialization test. The new stableIDDistinguishesSameObjectUnderDifferentParents test takes over the role of the deleted runtimeObjectKeyDiscriminatesIdentityPath test by asserting that two cells whose runtimeObject.key matches still hash to distinct StableIDs when their sidebar parents differ. Also fix RuntimeObject.withImagePath dropping the properties field — isGeneric / isSpecialized would otherwise be silently cleared on any imagePath rewrite.
…cated case Upstream's `derivingNestedSpecializationsWith` produces specialized nested children (e.g. `Phase<Event>.Value` from specializing `Phase<Event>`) whose generic ancestor is `Phase.Value`, not addressable from the derived parent. `makeRuntimeObject` used to register these as `.specializedType(unspecialized: self, specialized: self)`, which is incoherent — `unspecialized` should be the canonical generic parent, not the same bound typeName. The two current consumers (`interface(for:)`, `memberAddresses(...)`) mask this by checking `specializedDefinitionByObject` first, but any third consumer reading `unspecialized` would hit `invalidRuntimeObject`. Introduce `.derivedSpecializedType(TypeName)` as the dedicated case — docs make the contract explicit (only addressable via `specializedDefinitionByObject`). Route both existing consumers through the cache directly. Also clear `specializedDefinitionByObject` alongside `interfaceDefinitionNameByObject` on `showCImportedTypes` reconfig, otherwise the section keeps `TypeDefinition` references no consumer can reach.
a6b51bd to
9b955da
Compare
Summary
EventListenerPhase.Value-then-EventListenerPhaseordering.Root Cause
The sidebar splice path updated only the cell that directly received a specialization. Ancestor cells still held stale
RuntimeObject.childrensnapshots. When an ancestor later appended its own specialized child, rebuilding from that stale snapshot dropped descendant specializations such asEventListenerPhase.Value<Event>.Fix
The sidebar cell now materializes its current view-model subtree before appending a specialization, so ancestor updates preserve descendant children already spliced into the UI tree. Parent lookup also uses
RuntimeObjectKey, which intentionally ignores child snapshots.Dependency
Depends on MxIris-Reverse-Engineering/MachOSwiftSection#88 for the nested specialization SPI consumed by RuntimeViewerCore.
Validation
USING_LOCAL_DEPENDENCIES=1 DEVELOPER_DIR=/Applications/Xcode-26.4.1.app/Contents/Developer swift test --package-path RuntimeViewer/RuntimeViewerPackages --filter SidebarRuntimeObjectCellViewModelTestsDEVELOPER_DIR=/Applications/Xcode-26.4.1.app/Contents/Developer xcodebuild -quiet -workspace RuntimeViewer/RuntimeViewer-Debug.xcworkspace -scheme 'RuntimeViewer macOS' -configuration Debug -destination 'platform=macOS,arch=arm64' -derivedDataPath /tmp/RuntimeViewerNestedSpecializationBuildFinal build