diff --git a/Changelogs/v2.1.0-RC.1.md b/Changelogs/v2.1.0-RC.1.md index bbd71453..7a5c6f09 100644 --- a/Changelogs/v2.1.0-RC.1.md +++ b/Changelogs/v2.1.0-RC.1.md @@ -14,7 +14,7 @@ the stable channel with no reinstall: this build supersedes the last beta. - **Multi-tab content navigation** with a window tab bar and per-tab history - **Generic type specialization** — inspect Swift generics with concrete arguments filled in - **Background indexing** — the sidebar is ready when you click into it, not seconds later -- **Inspector → Relationships** — see who references a type, and what a type depends on +- **Inspector → Relationships** — a class's subclasses, a protocol's conforming types - **Batch export** interfaces for multiple images in one pass - **Theme settings** with a data-driven color model and a built-in Xcode preset - **Jump to Definition** for Swift type references @@ -42,9 +42,10 @@ interface alongside the original unspecialized form. silently producing garbage. #### Inspector → Relationships -A new **Relationships** tab sits between *Hierarchy* and *Specialization*. It -lists inbound references (who uses this type) and outbound references (what -this type depends on), drawn across every binary the document has loaded. +A new **Relationships** tab sits between *Hierarchy* and *Specialization*. +Select a class to list its direct subclasses; select a protocol to list the +types conforming to it. Results are gathered across every indexed image, not +just the one defining the type. #### Jump to Definition for Swift types Command-click a Swift type reference in the content view to navigate to its diff --git a/Changelogs/v2.1.0-beta.2.md b/Changelogs/v2.1.0-beta.2.md index 0cbb7644..35db79a4 100644 --- a/Changelogs/v2.1.0-beta.2.md +++ b/Changelogs/v2.1.0-beta.2.md @@ -14,12 +14,12 @@ This is a `beta` build — only clients that opted in via ### Inspector → Relationships A new **Relationships** tab sits between *Hierarchy* and *Specialization* and -shows where a type is referenced across the binaries you have loaded. +shows what else in the loaded binaries builds on a type. -- Surfaces inbound references (who uses this type) and outbound references - (what this type depends on), drawn from a dedicated relationships engine. -- Works across binaries: if the indexed images include the consumers of a - type, they show up regardless of which dylib defines it. +- Select a class to list its direct subclasses; select a protocol to list the + types conforming to it. +- Works across binaries: matches show up regardless of which dylib defines + them, as long as their image has been indexed. - The list lays out cleanly even when a type has no relationships — an empty state explains why instead of showing a blank tab. diff --git a/Changelogs/v2.1.0.md b/Changelogs/v2.1.0.md index 5fdf21ac..f14d68aa 100644 --- a/Changelogs/v2.1.0.md +++ b/Changelogs/v2.1.0.md @@ -1,17 +1,11 @@ # v2.1.0 -The first stable release on the **2.1** line, built up over nine public betas -and three release candidates and consolidated here. If you have been running -the stable channel since v2.0.1, everything below is new to you. +The first stable release on the **2.1** line, built up over nine betas and +three release candidates. If you have been on the stable channel since v2.0.1, +everything below is new to you. -Headline work spans runtime inspection, background indexing, multi-tab -navigation, appearance, and code injection — plus a late pass over what the app -looks like *while* it is working, which removed the flashes and grey blinks that -appeared whenever you clicked from one type to another. - -One upgrade step needs your attention: the privileged helper daemon reinstalls -itself on first launch. See **Upgrade notes** at the bottom — this applies to -release-candidate users too, not only to those coming from 2.0.x. +**On first launch the privileged helper reinstalls itself** — one admin prompt, +then a restart. This applies to RC users too; see *Upgrade notes*. --- @@ -19,13 +13,13 @@ release-candidate users too, not only to those coming from 2.0.x. - **Multi-tab content navigation** with a window tab bar and per-tab history - **Generic type specialization** — inspect Swift generics with concrete arguments filled in -- **Background indexing** — the sidebar is ready when you click into it, not seconds later -- **Inspector → Relationships** — a class's subclasses and a protocol's conforming types, gathered across every indexed binary +- **Background indexing** — the sidebar is ready when you click into it +- **Inspector → Relationships** — a class's subclasses, a protocol's conforming types - **Batch export** interfaces for multiple images in one pass -- **Theme settings** with a data-driven color model and a built-in Xcode preset +- **Theme settings** with a built-in Xcode preset - **Jump to Definition** for Swift type references - **Line numbers** in the content view -- **Injection into strict-seatbelt daemons** via `mach_vm_remap`, and into Apple applications via the same path +- **Injection into strict-seatbelt daemons and Apple applications** - **iOS Simulator runtime installer** built in --- @@ -34,342 +28,125 @@ release-candidate users too, not only to those coming from 2.0.x. ### Runtime inspection -#### Generic Type Specialization -Pick any Swift generic from the sidebar and a new **Specialize…** sheet lets -you fill in its type parameters. The Inspector renders the fully specialized -interface alongside the original unspecialized form. - -- Nested generics work — if an argument is itself generic, you can drill in - and fill those parameters in the same sheet. -- Type definitions can come from a different binary than the generic itself, - so you can specialize types in framework *A* with a model from framework - *B* loaded in the same document. -- Invalid combinations are caught up front with a clear error instead of - silently producing garbage. - -#### Inspector → Relationships -A new **Relationships** tab sits between *Hierarchy* and *Specialization*, -answering the question a single interface listing cannot: what else in the -loaded binaries builds on this type. - -- Select a **class** and it lists that class's direct subclasses. -- Select a **protocol** and it lists the types that conform to it. - -Both are unioned across every indexed image rather than looked up in the image -that defines the target, so a subclass or conformer living in a different -binary still shows up. Images that are loaded but not yet indexed contribute -nothing, so the list fills in as background indexing catches up. - -Objective-C and Swift classes and protocols are covered, including classes -bridged between the two. Other kinds — structs, enums, categories — have no -relationships to show. - -#### Jump to Definition for Swift types -Command-click a Swift type reference in the content view to navigate to its -definition, the same way you already could for Objective-C. - -#### Enum layout fixed-bit masks -The Swift enum-layout view now surfaces fixed-bit masks for each case, giving -you the exact bit pattern the runtime uses to discriminate cases without -having to read it out of the raw metadata. +**Generic Type Specialization** — pick a Swift generic from the sidebar and the +new **Specialize…** sheet lets you fill in its type parameters; the Inspector +then renders the fully specialized interface alongside the original. Nested +generics can be drilled into in the same sheet, and arguments may come from a +different binary than the generic itself. ---- +**Inspector → Relationships** — a new tab between *Hierarchy* and +*Specialization*. Select a class to list its direct subclasses; select a +protocol to list the types conforming to it. Results are gathered across every +indexed image, not just the one defining the type, so matches in other binaries +show up too — and the list fills in as background indexing catches up. Covers +Objective-C and Swift classes and protocols. -### Indexing & batch operations +**Jump to Definition for Swift types** — Command-click a Swift type reference +in the content view to navigate to its definition, as you already could for +Objective-C. -#### Background Indexing -Once a document opens, RuntimeViewer walks the dylib dependency graph and -starts indexing related binaries in the background, so by the time you click -into them they are ready to inspect. - -- A new toolbar progress popover shows what is currently indexing, lets you - cancel any individual batch, and keeps a history of recent runs. -- Selecting a binary that is still in the queue immediately bumps it to the - front. -- Background work runs at low priority and never makes the main UI stutter. -- A new **Settings → Indexing** page controls how aggressively background - indexing runs (or turns it off entirely). - -#### Always Index list + master switch -The Indexing settings page grew a master switch plus two independently -toggleable sub-modes: - -- **Heuristic Discovery** — main-executable BFS at document open and engine - swap, with configurable depth (1–5). -- **Always Index** — a user-editable list where each row pins a single image - (full path or file-name shorthand). Toggle **Follow Dependencies** on a row - to also walk its dependency closure. - -The batches popover groups its active and history sections by reason -(*Heuristic Discovery* / *Always Index* / *Manual*) under collapsible headers. - -#### Batch Export -Export the interfaces of multiple images in one pass without opening each -document individually. - -- New **File → Export Multiple Images…** entry walks you through image - selection, format selection, and destination directory in a single sheet. -- Per-language format choices (Objective-C `.h`, Swift surface, etc.) are - remembered across runs. -- The completion summary uses stat cards to show how many files succeeded, - failed, or were skipped, and lists each failure individually so you can - re-run only what broke. -- Long Swift-mangled names no longer produce APFS `NAME_MAX` errors mid-batch. - -#### iOS Simulator Runtime Installer -A new built-in installer downloads and installs missing iOS Simulator runtime -images directly from the app — no more chasing them down through Xcode. +**Enum layout fixed-bit masks** — the Swift enum-layout view now shows the exact +bit pattern the runtime uses to discriminate each case. ---- +### Indexing & batch operations -### Navigation & windowing +**Background Indexing** — RuntimeViewer walks the dylib dependency graph after a +document opens and indexes related binaries in the background, so they are ready +by the time you click into them. A toolbar popover shows what is indexing, lets +you cancel individual batches, and keeps a history; selecting a queued binary +bumps it to the front. -#### Multi-Tab Content Navigation -The main window now hosts multiple content tabs, so several runtime objects -can be open side by side instead of replacing one another in a single pane. - -- **Window tab bar** drives content navigation directly from the title bar. -- **Open in New Tab** is available both from content-view type links and from - sidebar rows, with a matching menu icon. -- Each tab keeps its own navigation timeline. -- Tabs are keyed by identity, and **Close Tab / Close Window** with **⌘W** - re-bind against the current key window automatically. - -#### Selection History & Navigation Menu -The toolbar drives back / forward navigation off a true selection-history -cursor, the way Xcode and Finder do. - -- Picking a sidebar item, drilling into a type, and switching between - Hierarchy / Relationships / Specialization tabs all push onto the same - stack. -- A **Navigation History** menu exposes the full per-tab timeline. -- The buttons enable / disable based on what's actually in the history, so - you never end up clicking back into nothing. -- The active inspector tab is preserved across selections. - -#### Smarter Sidebar Expansion -- Single-clicking a non-leaf row now expands it (matching the rest of macOS). -- A new setting caps how deep a double-click expands an entire subtree, so - you can preview a framework without exploding hundreds of children. - -#### Sidebar Grouping & Filter Scope -- Runtime objects are grouped into kind sections (classes, protocols, - categories…) instead of a flat list. -- A new **Filter Scope** popover in the sidebar lets you prefilter by kind - and property before typing, so the search corpus stays focused. - -#### Quit After Closing Last Window -**Settings → General → Windows** gains a *Quit After Closing Last Window* -toggle. It is off by default, which is the existing behaviour: the app stays -resident with no window open and a Dock icon click brings a window back. Turn -it on to have RuntimeViewer behave like a single-purpose utility that exits -with its last window. +**Settings → Indexing** — a new page with a master switch and two independent +modes: *Heuristic Discovery* (dependency BFS from the main executable, +configurable depth 1–5) and *Always Index* (a list of pinned images, each able +to follow its own dependencies). ---- +**Batch Export** — **File → Export Multiple Images…** exports the interfaces of +many images in one pass. Format choices are remembered across runs, and the +completion summary lists each failure individually so you can re-run only what +broke. -### Appearance & editing +**iOS Simulator Runtime Installer** — download and install missing Simulator +runtime images from within the app. -#### Theme Settings -A new **Theme** settings panel with a data-driven color model. Pick from -built-in presets (including an Xcode-matching one) or edit colors directly. -Themes drive the content view, inspector, and all syntax-highlighted -surfaces from a single source, and changes apply live. +### Navigation & windowing -#### Line Numbers in the Content View -A line-number gutter runs down the left edge of the interface text view, -handy for cross-referencing decompiled or exported output. +**Multi-Tab Content Navigation** — the main window hosts multiple content tabs, +so several runtime objects stay open side by side. **Open in New Tab** works +from both content-view type links and sidebar rows, and each tab keeps its own +navigation timeline. -#### Content-shaped loading placeholders -Panes that fetch their content asynchronously now stand in a placeholder shaped -like the content it replaces, instead of leaving the previous type's data on -screen until the new one arrives. +**Selection History** — back / forward run off a real history cursor, the way +Xcode and Finder do, with a **Navigation History** menu exposing the full +per-tab timeline. The active inspector tab is preserved across selections. -The placeholder is deliberately hard to catch sight of. Engine calls that hit a -warm cache return in single-digit milliseconds, so anything drawn the moment -work starts would appear and vanish inside one frame — a flash in its own right. -Work that finishes quickly now shows no placeholder at all, and a placeholder -that does appear stays long enough to read, measured from when it appeared, so -genuinely slow work is never delayed further. +**Sidebar** — runtime objects are grouped into kind sections instead of a flat +list; a **Filter Scope** popover prefilters by kind and property before you +type; single-clicking a non-leaf row expands it; and a setting caps how deep a +double-click expands a subtree. ---- +**Quit After Closing Last Window** — a new **Settings → General → Windows** +toggle, off by default. On, the app exits with its last window instead of +staying resident. -### Code injection +### Appearance -#### Injection into strict-seatbelt daemons and Apple applications -System daemons that opt into the strict seatbelt (e.g. `sharingd`, `rapportd`) -can now be injected via a `mach_vm_remap`-based path, which previously refused -to load the runtime dylib. Apple applications, which enforce AMFI library -validation, take the same path — see the corresponding bug fix below. -Everyday injection targets continue to work unchanged; the new path only kicks -in when the standard route is blocked. +**Theme Settings** — a new panel with built-in presets (including an +Xcode-matching one) and directly editable colors. One source drives the content +view, inspector, and every syntax-highlighted surface, and changes apply live. ---- +**Line Numbers** — a gutter down the left edge of the interface text view. -## Improvements +**Loading placeholders** — panes that fetch content asynchronously now show a +placeholder shaped like the content it replaces, instead of leaving the previous +type's data on screen. Work that finishes quickly shows no placeholder at all. -- Popovers (generation options, MCP status, background-indexing, type - picker) now size themselves to their content instead of defaulting to a - fixed 500×500 box. -- The Specialization type picker no longer blocks the UI when a generic - parameter has a wide constraint (10k+ candidates) — the list is built - off the main thread with a loading indicator, and rows are constructed - lazily as you scroll. -- Sidebar filtering skips the debounce when the query is empty, so - clearing the field feels instantaneous. -- The Mirror menu icon was refreshed and the toolbar no longer shows an - empty overflow chevron on macOS 15 when its items are hidden. +### Code injection + +**Strict-seatbelt daemons and Apple applications** — targets that refused the +standard injection path (`sharingd`, `rapportd`, and every Apple application, +which enforces library validation) now go through a `mach_vm_remap`-based path. +Everyday targets are unchanged; the new path only engages where the standard +route cannot work. --- ## Bug fixes -### Objective-C protocols reappear for UIKitCore, Foundation, and friends - -Opening UIKitCore showed an empty protocol list. Foundation showed no -`NSCoding`, `NSCopying`, or `NSFastEnumeration`. Classes, categories, and C -structs were listed normally, so the image itself parsed fine — only protocols -went missing, and they went missing entirely rather than partially. - -The cause was a protocol-ownership heuristic. Because the compiler emits a full -`protocol_t` into every image that saw a `@protocol` declaration at compile -time, `__objc_protolist` carries plenty of protocols an image merely imported. -The heuristic dropped a protocol whenever some other carrier of it sat in the -image's transitive dependency closure — which relied on an image never -appearing in its own closure. dyld's upward dependencies -(`LC_LOAD_UPWARD_DYLIB`) exist precisely to express cycles, so -UIKitCore ↔ PrintKitUI/ShareSheet and Foundation ↔ CoreFoundation each put an -image inside its own closure. Every protocol it carried was then classified as -imported, and the listing came back empty. - -Nothing in the Mach-O format records a defining image for a protocol, so -attribution can only ever be a heuristic — and this one failed silently, with -no error to hint that anything had been dropped. The filter is gone; -`__objc_protolist` is now listed verbatim. Some imported protocols reappear as -noise you can see and judge for yourself, which is the better failure mode: a -protocol that is silently absent is not. - -### Code injection picks its path from the whole rule - -Attaching to Music failed silently — nothing loaded on the target side, and no -error was reported. Two causes stacked. Routing between the two injection paths -consulted only the target's sandbox, which catches strict-seatbelt daemons but -says nothing about AMFI library validation; and Music, like every Apple -application, enforces library validation, which refuses a developer-signed -payload regardless of what the sandbox allows. The `dlopen` it was routed onto -could never have loaded anything, while the injector's shellcode reported -success anyway. - -The decision now lives in the helper daemon, which consults both the seatbelt -predicate and the code-signing flag, so Apple applications are routed onto the -`mach_vm_remap` path instead of one that cannot work. The investigation — the -`csops` status words separating the two target classes, the controlled -experiment isolating library validation from the payload's signature — is -written up in `Documentations/ResolvedIssues/`. - -### The Inspector no longer blanks when you switch types - -Clicking from one runtime object to another emptied the Inspector's list and -refilled it once the query returned. Relationships unions its results across -every indexed image, so that gap was long enough to read as a flash rather than -a redraw. - -The Inspector had been reusing its view controllers, but it still built a fresh -ViewModel per object and rebound it, which tore down the table's data binding -and installed a fresh one starting from an empty list. The tab ViewModels are -now bound once and handed the new object instead. Re-entering the same object — -closing a tab, moving the history cursor — short-circuits entirely, so it -neither refetches nor flashes a placeholder over content that is already -correct. - -This also fixed a real defect hiding behind the visual one: the old load had no -cancellation, so on rapid selection an earlier query could return late and -overwrite a newer object's rows. - -### Sidebar selection no longer blinks grey - -Two separate causes, both of which repainted the sidebar's selected row in the -inactive grey for a frame or two: - -- **Clicking a related type stole keyboard focus.** A row highlight is drawn - unemphasized whenever its table is not the first responder, so the Inspector - taking focus greyed out the sidebar row the user was actually looking at — for - focus the app handed straight back some 50ms later. The Relationships and - Specializations lists exist only to be clicked through, so they now refuse - first responder and paint no highlight of their own. Clicking, selecting and - navigating are unaffected; only arrow-key traversal inside those two lists is - gone, and every arrow press there navigated away anyway. -- **Jumping to a type selected the row before scrolling to it.** An outline view - materializes a row only as it scrolls into view and applies the emphasized - selection style after that, so the target row was drawn once in grey and - flipped to the accent colour on the next frame. Scrolling first — and forcing - the layout the scroll would otherwise defer — puts both in the same frame. - -### The tab strip no longer flashes when the tab set changes - -Changing the inspected kind rebuilds which Inspector tabs exist — selecting a -protocol after a class drops the Hierarchy tab. The strip used to remove every -tab and add the new set back, which swapped the visible view several times -inside one runloop pass. Tabs that survive the change now keep their view -throughout, and only tabs that genuinely appear or disappear cost anything. - -### Clicking the Dock icon reopens a window - -AppKit's built-in reopen handling only creates a window when it sees no visible -window at all, and it counts every window the app owns. With Settings or a -Sparkle update alert still on screen, clicking the Dock icon after closing every -document window therefore appeared to do nothing. RuntimeViewer now decides on -document windows alone, restoring a minimized or ordered-out one before falling -back to a new untitled document. - -### Attached daemons show their real icon - -Attaching to a daemon such as `launchservicesd` left the source menu showing an -App Store placeholder symbol. The icon cache is populated only from Launch -Services registered GUI applications, so a daemon never landed in it and every -lookup fell through to the placeholder. Daemons now fall back to the generic -executable icon — the same one the attach picker already shows for them. - -### A second instance no longer reports a phantom MCP server - -Binding the MCP bridge port fails whenever another RuntimeViewer instance -already holds it. That failure used to stop at a log line: the losing instance -still wrote the port file, still reported itself as running, and kept its fully -allocated network transport — 28 event loops, 56 threads, roughly 21 MiB — -resident for the lifetime of the process. It now tears the transport down, -truthfully reports itself stopped, and leaves the winning instance's port file -alone. A regression test covers the contention scenario end to end. - -### Smaller fixes - -- **Bonjour crash on batched receives.** Inspecting an iOS / visionOS / - Apple TV target over Bonjour, or pushing a batch-export through a - Bonjour connection, no longer crashes when a single network read - carries many small frames. -- **Attached-app and Mac Catalyst inspection.** The Inspector no longer - fails with *"An unknown error occurred"* when the runtime engine is - reached through a proxy hop; loading progress now streams across the - hop instead of getting stuck at *"Preparing…"*. -- **Nested sidebar specializations** keep their identity across sidebar - rebuilds instead of collapsing or re-shuffling. -- **Text view re-entry** rebuilds a fresh content view when returning - from another scene, fixing a case where the pane appeared blank. +- **Objective-C protocols reappear for UIKitCore, Foundation, and friends.** + Images caught in a dyld dependency cycle listed no protocols at all. +- **Injecting into Apple applications works.** Attaching to Music and its peers + failed silently — routed onto a path that could never load the payload, with + no error reported. +- **The Inspector no longer blanks when you switch types.** It also no longer + lets a slow query for an earlier type overwrite a newer one's results. +- **Sidebar selection no longer blinks grey** when you click a related type or + jump to a definition. +- **The tab strip no longer flashes** when switching to a kind with a different + set of Inspector tabs. +- **Clicking the Dock icon reopens a window** even when Settings or an update + alert is still on screen. +- **Attached daemons show their real icon** instead of an App Store placeholder. +- **A second instance no longer reports a phantom MCP server**, and no longer + keeps ~21 MiB of network transport resident after losing the port. +- **Bonjour no longer crashes on batched receives** when inspecting an iOS / + visionOS / Apple TV target, or exporting through a Bonjour connection. +- **Attached-app and Mac Catalyst inspection** no longer fails with *"An unknown + error occurred"*, and loading progress streams across the proxy hop instead of + sticking at *"Preparing…"*. +- **Nested sidebar specializations** keep their identity across sidebar rebuilds. +- **Returning from another scene** no longer leaves the content pane blank. - **Export module metadata resolution** fixed (#72). - **The Xcode theme preset** gained a light-mode selection color. -- **The Specialization type picker popover** no longer animates out of place. -- Numerous smaller stability fixes across the sidebar, inspector, and - document lifecycle. ---- - -## Build & Tooling +## Improvements -- Both package manifests agree on the same `swift-helper-service` floor, so the - two halves of the app can no longer resolve different helper protocols. -- The Core package pins every remote dependency it parses Mach-O with, so a - fresh checkout reproduces a known-good set instead of resolving freely. -- The release process, the documentation index, and the branching model are - written down under `Documentations/`. -- GitHub Releases are titled with the bare version tag. +- Popovers size themselves to their content instead of a fixed 500×500 box. +- The Specialization type picker no longer blocks the UI when a generic + parameter has 10k+ candidates, and its popover no longer animates out of place. +- Clearing the sidebar filter is instantaneous — no debounce on an empty query. +- The Mirror menu icon was refreshed, and the toolbar no longer shows an empty + overflow chevron on macOS 15. --- @@ -379,22 +156,14 @@ alone. A regression test covers the contention scenario end to end. - **MCP features**: macOS 15+ - **iOS variant**: iOS 18+ ---- - ## Upgrade notes -- **Helper Service reinstalls itself** — the privileged helper daemon moves to - protocol version `1.5.0`. On first launch after upgrade you will be prompted - for admin credentials once and then asked to restart the app. - - This applies **even if you were running `v2.1.0-RC.3`**. The daemon-side - change that routes Apple applications onto the `mach_vm_remap` injection path - shipped in RC.3, but the protocol version did not move with it, so an already - installed helper was never replaced and RC.3 users have been running the old - daemon. Bumping it here is what actually delivers that fix. - -- **Beta and RC channel users** — this build supersedes `v2.1.0-RC.3`; you can - switch back to the stable channel with no reinstall of the app itself. - -- **From v2.0.x** — no manual migration steps. Bookmarks, settings, and - connections carry over as-is. +- **The helper daemon reinstalls itself** on first launch — one admin prompt, + then a restart. This happens **even if you were running `v2.1.0-RC.3`**: the + daemon-side fix that routes Apple applications onto the working injection path + shipped in RC.3, but the version the app checks against did not move with it, + so installed helpers were never replaced. This release delivers it. +- **From a beta or RC** — this build supersedes `v2.1.0-RC.3`; switching back to + the stable channel needs no reinstall of the app itself. +- **From v2.0.x** — no migration steps. Bookmarks, settings, and connections + carry over as-is.