Skip to content

Fix "Settings could not be opened" after the LumaControl rename - #2

Merged
shay2000 merged 1 commit into
mainfrom
fix/settings-panes-module-name-mismatch
Sep 12, 2026
Merged

shay2000 merged 1 commit into
mainfrom
fix/settings-panes-module-name-mismatch

Conversation

@shay2000

Copy link
Copy Markdown
Owner

What the user saw

Clicking the gear in the menu bar — or reopening the app — put up:

Settings could not be opened
The app's preference panes failed to load from its interface file. Reinstalling or rebuilding the app should resolve this.

Settings became unreachable in 0.3.0. No pane would open at all.

Root cause

The LumaControl rebrand (542e00c) changed PRODUCT_NAME to LumaControl and, in the same commit, pinned SWIFT_MODULE_NAME = XDRMonitorControl on the app target. Those two settings feed different parts of the build, and they silently disagreed:

Setting Drives Value
PRODUCT_MODULE_NAME (follows PRODUCT_NAME) the module ibtool bakes into the compiled storyboard, because every scene is declared customModuleProvider="target" LumaControl
SWIFT_MODULE_NAME the Swift compiler's -module-name, and therefore the runtime Objective-C class names XDRMonitorControl

So the compiled storyboard asked for LumaControl.MainPrefsViewController while the binary only contained XDRMonitorControl.MainPrefsViewController.

NSStoryboard.instantiateController(withIdentifier:) does not raise in that situation. AppKit logs Unknown class … in Interface Builder file, returns a plain NSViewController, and the as? cast in main.swift yields nil — for all five panes. AppDelegate.settingsPanes then filters out everything, settingsWindowController is built with an empty pane list, and the guard in prefsClicked raises the alert.

Evidence from the shipped 0.3.0 (build 39) bundle:

$ nm -U /Applications/LumaControl.app/Contents/MacOS/LumaControl | grep OBJC_CLASS.*Prefs
_OBJC_CLASS_$__TtC17XDRMonitorControl23MainPrefsViewController      # module = XDRMonitorControl

$ strings .../Main.storyboardc/MainPrefsVC.nib | grep _TtC
_TtC11LumaControl23MainPrefsViewController                          # module = LumaControl

The fix

Remove the SWIFT_MODULE_NAME = XDRMonitorControl pin from both app-target configurations. The Swift module then follows PRODUCT_NAME again, which is exactly what the storyboard resolves against, so the two can no longer drift apart. The only other reference to the old module name was the test target's @testable import.

No storyboard change is needed — customModule="MonitorControl" in the XML source is inert, because customModuleProvider="target" makes ibtool substitute the real module name at compile time.

Regression test

MonitorControlTests/SettingsPanesTests.swift loads the five scenes exactly the way main.swift does, plus asserts each pane class's runtime module matches the app's own name.

I checked that it genuinely catches the bug by putting the SWIFT_MODULE_NAME pin back and re-running:

SettingsPanesTests.swift:34: error: XCTAssertNotNil failed - The MainPrefsVC scene did not load …
SettingsPanesTests.swift:37: error: XCTAssertNotNil failed - The MenuslidersPrefsVC scene did not load …
SettingsPanesTests.swift:40: error: XCTAssertNotNil failed - The KeyboardPrefsVC scene did not load …
SettingsPanesTests.swift:43: error: XCTAssertNotNil failed - The DisplaysPrefsVC scene did not load …
SettingsPanesTests.swift:46: error: XCTAssertNotNil failed - The AboutPrefsVC scene did not load …
SettingsPanesTests.swift:68: error: XCTAssertEqual failed: ("Optional("XDRMonitorControl")") is not equal to ("Optional("LumaControl")")
     Executed 2 tests, with 10 failures (0 unexpected)

Verification

  • xcodebuild test — 14/14 pass (12 existing + 2 new), no failures.
  • Compiled storyboard and app binary now both name _TtC11LumaControl23MainPrefsViewController.
  • Installed the Release build locally (0.3.0, build 44, universal, signed with the same Apple Development cert so the Accessibility grant survives) and triggered the Settings window for real: it opens on the General pane with no alert.

Risk

Low. One build setting is deleted and a test file is added; no runtime code, no storyboard, and no bundle identifier change. The bundle ID stays com.shay2000.XDRMonitorControl on purpose, to keep the Accessibility grant and the helper login item alive across the rename.

Rollback is a revert of this commit.

The gear button and reopening the app showed "Settings could not be opened -
The app's preference panes failed to load from its interface file."

Root cause: the rebrand changed PRODUCT_NAME to LumaControl and, in the same
commit, pinned SWIFT_MODULE_NAME = XDRMonitorControl. Those two settings feed
different parts of the build:

  * PRODUCT_MODULE_NAME follows PRODUCT_NAME, and that is the module ibtool
    bakes into the compiled storyboard, because every scene is declared with
    customModuleProvider="target".
  * SWIFT_MODULE_NAME is what the Swift compiler uses for -module-name, so the
    pane classes became XDRMonitorControl.MainPrefsViewController at runtime.

The storyboard therefore asked for LumaControl.MainPrefsViewController, AppKit
found no such class, logged "Unknown class ... in Interface Builder file",
handed back a bare NSViewController, and the `as?` cast in main.swift produced
nil for all five panes. AppDelegate dropped every pane and raised the alert.

Dropping SWIFT_MODULE_NAME lets the Swift module follow PRODUCT_NAME again, so
the two cannot drift apart. The only other reference to the old module name was
the test target's `@testable import`.

Adds MonitorControlTests/SettingsPanesTests.swift, which loads the five scenes
exactly the way main.swift does and also compares each pane class's runtime
module against the app's own name. Putting the pin back makes it fail with all
five panes nil and the mismatched module name spelled out, so this cannot
regress silently again.

Verified: 14/14 tests pass; the compiled storyboard and the app binary now both
name _TtC11LumaControl23MainPrefsViewController; the Settings window opens on
the installed 0.3.0 (build 44) build.
@greptile-apps

greptile-apps Bot commented Sep 12, 2026

Copy link
Copy Markdown

Greptile Summary

This PR repairs Settings pane loading after the application rename by allowing the Swift module name to follow PRODUCT_NAME, keeping runtime class names aligned with the module embedded in the compiled storyboard.

  • Removes the stale SWIFT_MODULE_NAME = XDRMonitorControl override from Debug and Release.
  • Updates the existing test target to import LumaControl.
  • Adds regression coverage that instantiates all five Settings scenes and verifies their runtime module.

Confidence Score: 5/5

The PR appears safe to merge and consistently fixes the storyboard-to-runtime module mismatch.

The app’s Debug and Release configurations now derive the same LumaControl module used by the storyboard, all test imports agree with that module, and the hosted regression tests exercise the production storyboard loading path.

Important Files Changed

Filename Overview
MonitorControl.xcodeproj/project.pbxproj Removes the conflicting app module override in both configurations and registers the new regression test source.
MonitorControlTests/SettingsPanesTests.swift Adds hosted-app tests covering all five Settings storyboard scenes and module-name alignment.
MonitorControlTests/XDRBrightnessTests.swift Updates the testable import to match the app’s corrected LumaControl module name.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  A["PRODUCT_NAME = LumaControl"] --> B["PRODUCT_MODULE_NAME = LumaControl"]
  B --> C["ibtool embeds LumaControl in storyboard classes"]
  B --> D["Swift runtime classes use LumaControl module"]
  C --> E["Settings scenes resolve successfully"]
  D --> E
Loading

Reviews (1): Last reviewed commit: "Fix "Settings could not be opened" after..." | Re-trigger Greptile

@shay2000
shay2000 merged commit cf88498 into main Sep 12, 2026
3 checks passed
@shay2000
shay2000 deleted the fix/settings-panes-module-name-mismatch branch September 12, 2026 12:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant