Modernised UI: SwiftUI entry, SettingsView, LogManager/Console#136
Conversation
Replace legacy App.icns + InjectionBusy/Error/Idle/OK/Ready.tif with
a proper asset catalog: AppIcon.appiconset (10 sizes) and six status
imagesets (INJECTION_BLUE/GREEN/ORANGE/PURPLE/RED/YELLOW).
Info.plist: swap CFBundleIconFile ("App.icns") for CFBundleIconName
("AppIcon").
pbxproj: drop file refs + build-phase entries for the removed icons.
Visual output only; no runtime behaviour change. Status image lookups
by these names will be wired up in the SwiftUI StatusMenuView commit.
Made-with: Cursor
Replace the small Defaults struct with an ObservableObject ConfigStore that is the single source of truth for all user-facing configuration. It owns persistence to UserDefaults, publishes change notifications for SwiftUI Views (landing in a later commit), and exposes discovery helpers for available Xcodes and codesigning identities. To keep call-site churn out of this commit, ConfigStore ships with a backward-compat Defaults shim that forwards to ConfigStore.shared, so AppDelegate / MonitorXcode / NextCompiler / ControlServer / etc. keep compiling unchanged. Those will be cleaned up alongside the SwiftUI entry point. Project changes: - Delete App/InjectionNext/Defaults.swift - Add App/InjectionNext/ConfigStore.swift (Xcode, Build System, Compiler, Injection, Devices, Tracing, File Watcher, Network and Advanced sections + project discovery helpers + picker window used when multiple .xcodeproj/.xcworkspace are found) - Bump InjectionNext app deployment target 10.13 -> 13.0 (required by @published / SwiftUI MenuBarExtra work that follows) - Switch the DLKit SPM dependency from tagged 3.5.6 to branch 'main' so we resolve the commit that exposes the configurable dlOpenMode knob ConfigStore binds to (johnno1962/DLKit hasn't cut a tag yet). No behavioural change yet; the UI is still XIB-based and reads the same UserDefaults keys. Made-with: Cursor
Migrate the app's launch path from main.m + MainMenu.xib to a SwiftUI @main entry (InjectionNextApp) backed by a MenuBarExtra and an NSApplicationDelegateAdaptor. The menu bar dropdown is now driven by StatusMenuView, which observes ConfigStore for status, watchers, build system, and client state. AppDelegate is slimmed to a shim over ConfigStore: IBOutlets are gone and their call sites keep compiling via lightweight Compat* types (CompatMenuItem/Button/TextField) whose reads/writes route to ConfigStore. main.m, MainMenu.xib, and the XIB-specific Info.plist keys (NSMainNibFile, NSPrincipalClass) are removed. Minimal non-UI adjustments to keep the link clean: - ControlServer: pass a fresh NSMenuItem() to deviceEnable instead of the removed outlet, drop the selectXcodeItem.toolTip write. - FrontendServer: drop optional-chaining on patchCompilerItem (shim returns a non-optional NSMenuItem). Made-with: Cursor
Ports the modern SwiftUI settings UI from main into 2.0_overhaul:
- SettingsView as the shell with tabbed navigation
- General, Xcode, BuildSystem, Compiler, Injection, Devices,
FileWatcher, Network, Tracing, Advanced settings panes
- Bridges device enable toggle to AppDelegate.applyDeviceSettings(enabled:)
- Adds a SwiftUI Window scene ("settings") to InjectionNextApp
All panels observe ConfigStore and mutate published properties so the
legacy non-UI backend continues to see state changes via the
CompatMenuItem/CompatButton/CompatTextField shims in AppDelegate.
Made-with: Cursor
- LogManager: centralized ObservableObject logger with timestamped entries, ring buffer, duplicate suppression, uncaught-exception / fatal-signal handlers, and a readLoop that hijacks STDOUT/STDERR while still mirroring bytes to the real fds. - Removes the old ad-hoc LogBuffer class from ControlServer.swift; kept LogBuffer as a typealias to LogManager for back-compat. - Updates InjectionServer / NextCompiler / ControlServer call sites to the non-optional shared logger. - AppDelegate now starts log capture unconditionally; ControlServer start remains gated on Defaults.mcpServer. - Adds ConsoleView (SwiftUI) with filter/level/auto-scroll and an NSTextView-backed selectable log pane. - InjectionNextApp exposes a new "console" Window scene; the status menu's "Open Console…" entry opens it via openWindow. Made-with: Cursor
Made-with: Cursor
Persist CODE_SIGN_IDENTITY=- and an empty DEVELOPMENT_TEAM for the macOS SDK in the InjectionNext target so local builds no longer require passing those flags on the xcodebuild command line. Also absorbs an Xcode-initiated reordering of existing pbxproj sections (no functional change). Made-with: Cursor
…s-dd/2.0_overhaul-ui
Made-with: Cursor
|
Looks good. Did you need to change the code signing though? What was the problem with Defaults.swift? |
|
Seems there is a missing setting "Device Testing". Looking at on-device injection you should only link the injection.dylib with |
Previously, enabling "Device Injection" also unconditionally linked -framework XCTest -lXCTestSwiftSupport into every injection dylib, so apps that don't link XCTest themselves would crash at dlopen with: Library not loaded: @rpath/XCTest.framework/XCTest NextCompiler already checks `AppDelegate.ui.deviceTesting?.state == .on` to decide whether to add the testing -F/-L flags, but the SwiftUI port routed that shim to `devicesEnabled`, so it was always on when devices were on. Split into its own flag: - ConfigStore.deviceTesting (Bool, persisted as "deviceTesting") - Defaults.deviceTesting back-compat shim - AppDelegate.deviceTesting shim now binds to ConfigStore.deviceTesting - AppDelegate.deviceTestingToggled(enabled:) exposes the legacy testingEnable behaviour (copy Run Script build phase to clipboard when turning on) to SwiftUI - DevicesSettingsView: new "Device Testing" section with the toggle, footer explaining the copy_bundle.sh requirement, and greyed-out Linker Libraries field when off Made-with: Cursor
|
Hey @johnno1962!
No, not functionally, the app still signs the same way when built through Xcode. I pinned CODE_SIGN_IDENTITY[sdk=macosx*] = "-" + empty DEVELOPMENT_TEAM so xcodebuild from the CLI works without my personal team. Before that, a plain xcodebuild … build failed with No "Mac Development" signing certificate matching team ID "9V5A8WE85E". Happy to drop the commit if you'd rather each contributor pin their own team.
Nothing wrong with it per se, it just isn't observable. Defaults is a struct of static computed UserDefaults accessors, so SwiftUI has no way to re-render when a value changes (no @published, no objectWillChange). The new ConfigStore is a singleton ObservableObject over the same keys, which is what every new SwiftUI view binds to via @ObservedObject/@published. Persistence semantics are identical (same UserDefaults keys, same defaults). I kept a Defaults shim in ConfigStore.swift that forwards to ConfigStore.shared, so legacy call sites (Defaults.xcodeDefault, Defaults.deviceLibraries, etc.) still compile untouched. |
There was a problem hiding this comment.
Pull request overview
Ports the modernized macOS UI overhaul from AppKit/XIB to SwiftUI, introducing a SwiftUI app entry point, a multi-panel Settings window, and an in-app Console backed by a centralized logger/config store.
Changes:
- Replace
main.m+MainMenu.xibstatus menu with SwiftUI@main+MenuBarExtra(StatusMenuView) and add dedicatedsettings/consolewindow scenes. - Introduce
ConfigStore(ObservableObject) to centralize/persist settings and keep legacy call sites compiling via compatibility shims. - Add
LogManagerwith a ring buffer + stdout/stderr capture and migrate log call sites to a non-optional shared logger.
Reviewed changes
Copilot reviewed 35 out of 57 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| TODO.md | Adds a 2.0 roadmap and tracking notes for follow-up work. |
| App/InjectionNext/main.m | Removes legacy Cocoa entry point in favor of SwiftUI @main. |
| App/InjectionNext/InjectionNextApp.swift | Adds SwiftUI app entry with menu bar extra + settings/console windows. |
| App/InjectionNext/AppDelegate.swift | Removes XIB outlets; adds ConfigStore-backed compatibility shims and lifecycle setup. |
| App/InjectionNext/ConfigStore.swift | Adds centralized observable/persisted configuration + project discovery/picker UI. |
| App/InjectionNext/LogManager.swift | Adds centralized logger with ring buffer, dedupe, crash capture, and stdout/stderr interception. |
| App/InjectionNext/Views/StatusMenuView.swift | Implements SwiftUI replacement for the legacy status menu. |
| App/InjectionNext/Views/SettingsView.swift | Adds Settings window shell (sidebar + section routing). |
| App/InjectionNext/Views/GeneralSettingsView.swift | Adds “General” settings/status panel. |
| App/InjectionNext/Views/XcodeSettingsView.swift | Adds Xcode selection + launch/restart toggles and runtime status display. |
| App/InjectionNext/Views/BuildSystemSettingsView.swift | Adds build-system selection (auto/Xcode/Bazel/SPM) and tool-path configuration. |
| App/InjectionNext/Views/CompilerSettingsView.swift | Adds compiler interception UI and frontend path/status display. |
| App/InjectionNext/Views/InjectionSettingsView.swift | Adds project path selection, watcher management, and injection behavior toggles. |
| App/InjectionNext/Views/DevicesSettingsView.swift | Adds device injection/testing toggles and codesigning identity selection UI. |
| App/InjectionNext/Views/FileWatcherSettingsView.swift | Adds injectable-pattern and latency settings UI. |
| App/InjectionNext/Views/NetworkSettingsView.swift | Adds host/port/protocol info UI and client connection status. |
| App/InjectionNext/Views/TracingSettingsView.swift | Adds trace mode + filter/framework/UIKit tracing configuration UI. |
| App/InjectionNext/Views/AdvancedSettingsView.swift | Adds advanced toggles, dlopen mode selection, diagnostics, and reset-to-defaults UI. |
| App/InjectionNext/Views/ConsoleView.swift | Adds in-app console UI with filtering and selectable NSTextView-backed log pane. |
| App/InjectionNext/NextCompiler.swift | Migrates logging call sites from optional LogBuffer.shared? to non-optional .shared. |
| App/InjectionNext/InjectionServer.swift | Routes alert/error/log messages into LogManager (via LogBuffer typealias). |
| App/InjectionNext/ControlServer.swift | Removes ad-hoc LogBuffer class; uses LogManager for log APIs and updates device toggle behavior. |
| App/InjectionNext/FrontendServer.swift | Adjusts patch/unpatch UI title update to the new (shimmed) menu item surface. |
| App/InjectionNext/Info.plist | Switches to asset-catalog icon name and removes NSMainNibFile/NSPrincipalClass. |
| App/InjectionNext/Defaults.swift | Removes old Defaults implementation (superseded by ConfigStore + back-compat shim). |
| App/InjectionNext/Base.lproj/MainMenu.xib | Removes legacy XIB-based menu UI. |
| App/InjectionNext/App.icns | Removes legacy .icns app icon in favor of an asset catalog. |
| App/InjectionNext/InjectionIdle.tif | Removes legacy TIFF status icon asset (migrated to asset catalog). |
| App/InjectionNext/InjectionOK.tif | Removes legacy TIFF status icon asset (migrated to asset catalog). |
| App/InjectionNext/InjectionBusy.tif | Removes legacy TIFF status icon asset (migrated to asset catalog). |
| App/InjectionNext/InjectionReady.tif | Removes legacy TIFF status icon asset (migrated to asset catalog). |
| App/InjectionNext/InjectionError.tif | Removes legacy TIFF status icon asset (migrated to asset catalog). |
| App/InjectionNext/Assets.xcassets/Contents.json | Adds new asset catalog container metadata. |
| App/InjectionNext/Assets.xcassets/INJECTION_BLUE.imageset/Contents.json | Adds status icon imageset metadata (blue). |
| App/InjectionNext/Assets.xcassets/INJECTION_BLUE.imageset/INJECTION_BLUE.png | Adds status icon image (blue). |
| App/InjectionNext/Assets.xcassets/INJECTION_GREEN.imageset/Contents.json | Adds status icon imageset metadata (green). |
| App/InjectionNext/Assets.xcassets/INJECTION_GREEN.imageset/INJECTION_GREEN.png | Adds status icon image (green). |
| App/InjectionNext/Assets.xcassets/INJECTION_ORANGE.imageset/Contents.json | Adds status icon imageset metadata (orange). |
| App/InjectionNext/Assets.xcassets/INJECTION_ORANGE.imageset/INJECTION_ORANGE.png | Adds status icon image (orange). |
| App/InjectionNext/Assets.xcassets/INJECTION_PURPLE.imageset/Contents.json | Adds status icon imageset metadata (purple). |
| App/InjectionNext/Assets.xcassets/INJECTION_PURPLE.imageset/INJECTION_PURPLE.png | Adds status icon image (purple). |
| App/InjectionNext/Assets.xcassets/INJECTION_RED.imageset/Contents.json | Adds status icon imageset metadata (red). |
| App/InjectionNext/Assets.xcassets/INJECTION_RED.imageset/INJECTION_RED.png | Adds status icon image (red). |
| App/InjectionNext/Assets.xcassets/INJECTION_YELLOW.imageset/Contents.json | Adds status icon imageset metadata (yellow). |
| App/InjectionNext/Assets.xcassets/INJECTION_YELLOW.imageset/INJECTION_YELLOW.png | Adds status icon image (yellow). |
| App/InjectionNext/Assets.xcassets/AppIcon.appiconset/Contents.json | Migrates app icon to asset catalog app icon set metadata. |
| App/InjectionNext/Assets.xcassets/AppIcon.appiconset/icon_16x16.png | Adds app icon raster (16x16). |
| App/InjectionNext/Assets.xcassets/AppIcon.appiconset/icon_16x16@2x.png | Adds app icon raster (32x32 for 16@2x). |
| App/InjectionNext/Assets.xcassets/AppIcon.appiconset/icon_32x32.png | Adds app icon raster (32x32). |
| App/InjectionNext/Assets.xcassets/AppIcon.appiconset/icon_32x32@2x.png | Adds app icon raster (64x64 for 32@2x). |
| App/InjectionNext/Assets.xcassets/AppIcon.appiconset/icon_128x128.png | Adds app icon raster (128x128). |
| App/InjectionNext/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png | Adds app icon raster (256x256 for 128@2x). |
| App/InjectionNext/Assets.xcassets/AppIcon.appiconset/icon_256x256.png | Adds app icon raster (256x256). |
| App/InjectionNext/Assets.xcassets/AppIcon.appiconset/icon_256x256@2x.png | Adds app icon raster (512x512 for 256@2x). |
| App/InjectionNext/Assets.xcassets/AppIcon.appiconset/icon_512x512.png | Adds app icon raster (512x512). |
| App/InjectionNext/Assets.xcassets/AppIcon.appiconset/icon_512x512@2x.png | Adds app icon raster (1024x1024 for 512@2x). |
| App/InjectionNext.xcodeproj/project.pbxproj | Updates build settings, deployment target, resources, SwiftUI sources, and DLKit dependency pin. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
We'll have to think about the code signing issue. At present, I build releases using "Product/Archive" which also builds all the bundles. That's something small to sort out. The deviceTesting fix works now thanks. Before, you needed to "Enable Devices" each time you launch the app by design and it is now persistent - that may be ok I guess. The bad news though is I had a hang once which I've not seen again here inside this closure somewhere in Combine: If you want me to I'll merge at this point let me know though I figure it can wait now the submodules are up to date. I'm amazed at how much you've changed and the app still works as far as I can make out. |
|
Had to merge a couple of changes from main due to some of the code being shared with InjectionIII which has created a pair of conflicts which I hadn't anticipated.Apologies, they were very small changes which should be easy to rebase. |
Conflicts: - project.pbxproj: take upstream's DLKit 3.5.7 pin instead of our `branch = main` — 3.5.7 is where `dlOpenMode` landed, so the floating branch reference is no longer needed. - NextCompiler.swift: keep upstream's move of LogBuffer.append into the #else branch (so INJECTION_III_APP doesn't log), combined with the non-optional `.shared` call this branch already migrated to. Made-with: Cursor
|
Sorry I've had to do it again as the one line edit to NextCompiler.swift was wrong, this time including Info.plist. Let me know if you want me to attempt to resolve this. The version of the change I force pushed to main should have been: |
d0bc379 to
162fa3c
Compare
4344a36 to
162fa3c
Compare
|
PR is not quite correct in NextCompiler.swift, it's obvious how. I think I'll quit while we're ahead and can merge (phew) |
Merge-resolve aftermath: two LogBuffer.append calls were coexisting inside the #else branch — the original pre-prefix one from an earlier resolution and John's corrected post-prefix one from johnno1962#138. Keep only the post-prefix call so log entries carry the APP_PREFIX, and keep the non-optional .shared migration from this branch. Made-with: Cursor
|
Thanks @johnno1962 so much for bearing with the merge ping-pong, and for the patience on both passes. Totally happy to keep iterating, happy to rework Also want to flag: the |
|
Looking good now, thanks for the fix. I've been doing some testing and the red Xcode was related to running injectionNext.app from the debugger, killing Xcode than launching it from inside the app. Everything is fine if you use the command line if you re-enable code signing. I was wondering if the Settings menu item shouldn't be closer to the top, before the "Launch Xcode" its even and if you want to make your InjectionState fix though I'm not convinced that can be the answer if it was an asynchronous call to main. If you want to make those changes I'd like to merge your PR to de-risk this branch a little and then I can look at what I need for code signing releases for which I use the Xcode distribution workflow and see if we can reach an agreement. My idea at the moment is to create a separate Archive configuration perhaps though I think in the end you'll need to update the team. It's a project not target setting so it's not so inconvenient to change. I wouldn't be trying to automate releases with a GitHub workflow at this stage. I'd certainly like to avoid a situation where there are competing releases. Then I think we'd be ready to roll a release candidate and start to get feedback of the new version. What do you mean by "happy to rework NextCompiler.swift in a follow-up"? It's not a model piece of code but if we start changing the functional side of things that involves a re-test and there are many modalities to check. I'd rather focus on the UI renovation and Bazel changes for now. How do you want me to merge? merge/squash/rebase? This branch itself will get squashed onto main as I like to keep that uncluttered. |
|
I've squashed this PR for now so I can build a release candidate. You should be able to add further commits and raise a new continuation PR to this same release branch. |
|
Thanks @johnno1962. Pushing two small follow-ups now:
On NextCompiler.swift: ignore my earlier "rework" line, I only meant "I'm open to more iteration if you wanted it." Nothing functional is wrong; happy to leave it as-is on this PR. If anything backend creeps in later I'll do it as a focused follow-up PR so you can scope the re-test. On code signing: +1 to the separate Archive configuration route, keeps your distribution workflow intact. For day-to-day command-line builds I'll switch to updating the project team to ours (the 9V5A8WE85E default caught me off guard). Happy to revisit once you've sketched the Archive config and the release cadence you prefer. On GitHub release automation: small clarification, I actually did wire it up on my main fork have a look if you're curious, so the mechanics work. The reason I'm not pushing for it here is a UX one: without the Apple Developer ID + notarization secrets in the GitHub Actions environment, the .app published by the workflow isn't signed by a trusted identity. macOS Gatekeeper then requires users to right-click → Open and manually grant permission on every launch, which is a bad first impression. Agreed it's not the right moment to fight with that, keeping releases under your archive workflow until we sort signing (and maybe notarization creds) is the sane path. Merge preference: squash, the branch will end up squashed onto main anyway, no point preserving the merge-resolution ping-pong in the history. Your call though; merge is also fine. |
|
Hi, I squashed while you were writing that and prepared a release and made a commit reverting you code signing changes for now. Not sure how automated release workflow will work without the team set which is required to build the iOS bundles. You could xcodebuild DEVELOPMENT_TEAM=YOURTEAM in your workflow I guess though build_bundles.sh might need a small change to pass it through. On the hang it's a shame I didn't gather more information at the time but the change was definitely where I said. The code you suggested seems a sensible precaution however which may make it "go away". If you want to add a few commits to 2.0_overhual the release is a draft at the moment and I can incorporate them. The threading fix could be one thing I'd like to see included in the RC and shifting the Settings menu item up to be more prominent - No hurry. |
Summary
Ports the AppKit/XIB → SwiftUI overhaul from
maatheusgois-dd/mainonto2.0_overhaul, split into 5 logical commits plus a small build-config commit. No behavioural changes in the backend (MonitorXcode,NextCompiler,FrontendServer,ControlServer) — the AppDelegate keeps its@IBActionsignatures and getsCompatMenuItem/CompatButton/CompatTextFieldshims that route reads/writes toConfigStore.shared, so legacy mutation call sites still compile.Commits (in order):
chore(assets): migrate toAppIconcatalog +INJECTION_*imagesets; dropApp.icns+ legacy.tiffiles.refactor(config): introduceConfigStore(ObservableObject), dropDefaults.swift, keepDefaultsas a typealiased back-compat shim.feat(ui): SwiftUI@main(InjectionNextApp) +StatusMenuView; deleteMainMenu.xib,main.m, and the AppDelegate@IBOutletsurface; addCompat*shims.Info.plistdropsNSMainNibFile/NSPrincipalClass.UI: SettingsView— 10 panels (General,Xcode,BuildSystem,Compiler,Injection,Devices,FileWatcher,Network,Tracing,Advanced) observingConfigStore; newWindow(\"settings\")scene. AddsAppDelegate.applyDeviceSettings(enabled:restartServer:)soDevicesSettingsViewdoesn't have to forge anNSMenuItem.UI: LogManager + ConsoleView— centralObservableObjectlogger with ring buffer, dedupe, uncaught-exception / fatal-signal capture, stdout/stderr hijack mirrored to the real fds;Window(\"console\")scene; removes the ad-hocLogBufferclass fromControlServer.swiftin favour oftypealias LogBuffer = LogManager. Call sites migrated from optional?.to non-optional.shared.build: persistCODE_SIGN_IDENTITY=-and emptyDEVELOPMENT_TEAMfor the macOS SDK so localxcodebuildno longer needs those flags.docs: addTODO.mdroadmap.MACOSX_DEPLOYMENT_TARGETbumped from 10.13 → 13.0 (required for@Published/MenuBarExtra).DLKitSPM ref moved tomainto pick up the publicdlOpenModeoverride.Roadmap (from TODO.md)
Done
static var dlOpenModeoverride — merged upstream, consumed via SPM.Next
MARKETING_VERSION, tag after merge).ConfigStore; migrate remainingUserDefaultsdirect reads in the backend.Test plan
xcodebuild -project App/InjectionNext.xcodeproj -scheme InjectionNext clean buildon Xcode 26.3 →BUILD SUCCEEDED..app, smoke-test menu bar entries (Launch Xcode, Select Project…, Watch Project…, Unhide Symbols, Open Console…, Settings…, Quit).print/NSLog/ Swift runtime warnings appear; Clear works.Made with Cursor