From 88807661b52c343c1a78876c3cb4dc60f8bbe01e Mon Sep 17 00:00:00 2001 From: Jason Rhubottom Date: Fri, 13 Mar 2026 22:00:27 -0700 Subject: [PATCH 1/8] Fix build: remove empty scheme scripts, bump deployment target, migrate Sparkle to SPM Remove empty shell script pre/post actions from Fluor.xcscheme that caused build failures. Update MACOSX_DEPLOYMENT_TARGET from 10.12 to 10.13 to match SmoothOperators minimum requirement. Replace manual Sparkle.framework reference (hardcoded to original developer's path) with Swift Package Manager dependency on Sparkle 2.x, and update storyboard references from SUUpdater to SPUStandardUpdaterController. --- CLAUDE.md | 3 + Fluor.xcodeproj/project.pbxproj | 61 +++++++--------- .../xcshareddata/swiftpm/Package.resolved | 70 +++++++++++-------- .../xcshareddata/xcschemes/Fluor.xcscheme | 36 ---------- Fluor/Base.lproj/Preferences.storyboard | 4 +- Fluor/Info.plist | 2 +- 6 files changed, 71 insertions(+), 105 deletions(-) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..55aebc6 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,3 @@ +## Commit Guidelines + +- Do not include "Co-Authored-By" lines or any Claude/AI attribution in commit messages. diff --git a/Fluor.xcodeproj/project.pbxproj b/Fluor.xcodeproj/project.pbxproj index e9e7280..0249687 100644 --- a/Fluor.xcodeproj/project.pbxproj +++ b/Fluor.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 52; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ @@ -42,8 +42,7 @@ 3F8F93BB1EEAC9B900FCE91F /* RuleCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F8F93BA1EEAC9B900FCE91F /* RuleCellView.swift */; }; 3F8F93BD1EEAF1EB00FCE91F /* RuleValueTransformer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F8F93BC1EEAF1EB00FCE91F /* RuleValueTransformer.swift */; }; 3F9EDD2A245C7BAF0047D1AC /* MenuItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F9EDD29245C7BAF0047D1AC /* MenuItemView.swift */; }; - 3FBE4C262222A3C600782647 /* Sparkle.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3FBE4C202222A22200782647 /* Sparkle.framework */; }; - 3FBE4C272222A3C600782647 /* Sparkle.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3FBE4C202222A22200782647 /* Sparkle.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 3FBE4C262222A3C600782647 /* Sparkle in Frameworks */ = {isa = PBXBuildFile; productRef = 3FBE4C302222A3C600782647 /* Sparkle */; }; 3FBE4C292222AB0200782647 /* dsa_pub.pem in Resources */ = {isa = PBXBuildFile; fileRef = 3FBE4C282222AB0200782647 /* dsa_pub.pem */; }; 3FC44EFA1D7F169A0065D433 /* Enums.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FC44EF91D7F169A0065D433 /* Enums.swift */; }; 3FC44EFC1D7F16CB0065D433 /* Items.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3FC44EFB1D7F16CB0065D433 /* Items.swift */; }; @@ -68,20 +67,6 @@ 3FF655CA20751D1600C8D2FC /* PFMoveApplication.m in Sources */ = {isa = PBXBuildFile; fileRef = 3FF655C820751D1600C8D2FC /* PFMoveApplication.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; /* End PBXBuildFile section */ -/* Begin PBXCopyFilesBuildPhase section */ - 3FBE4C252222A25200782647 /* Embed Frameworks */ = { - isa = PBXCopyFilesBuildPhase; - buildActionMask = 2147483647; - dstPath = ""; - dstSubfolderSpec = 10; - files = ( - 3FBE4C272222A3C600782647 /* Sparkle.framework in Embed Frameworks */, - ); - name = "Embed Frameworks"; - runOnlyForDeploymentPostprocessing = 0; - }; -/* End PBXCopyFilesBuildPhase section */ - /* Begin PBXFileReference section */ 3DF3612324C206AD00231BF5 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/MainMenu.strings"; sourceTree = ""; }; 3DF3612424C206AD00231BF5 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/RunningApps.strings"; sourceTree = ""; }; @@ -126,7 +111,6 @@ 3F8F93BA1EEAC9B900FCE91F /* RuleCellView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RuleCellView.swift; sourceTree = ""; }; 3F8F93BC1EEAF1EB00FCE91F /* RuleValueTransformer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RuleValueTransformer.swift; sourceTree = ""; }; 3F9EDD29245C7BAF0047D1AC /* MenuItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuItemView.swift; sourceTree = ""; }; - 3FBE4C202222A22200782647 /* Sparkle.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Sparkle.framework; path = ../../../../pyroh/Dev/Projects/Fluor/Fluor/Sparkle.framework; sourceTree = ""; }; 3FBE4C282222AB0200782647 /* dsa_pub.pem */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = dsa_pub.pem; sourceTree = ""; }; 3FC44EF91D7F169A0065D433 /* Enums.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Enums.swift; sourceTree = ""; }; 3FC44EFB1D7F16CB0065D433 /* Items.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Items.swift; sourceTree = ""; }; @@ -165,7 +149,7 @@ files = ( 3FE7B6DB245DF8450027DB39 /* SmoothOperators in Frameworks */, 3F16ECDD23E9D1AC008BC89A /* DefaultsWrapper in Frameworks */, - 3FBE4C262222A3C600782647 /* Sparkle.framework in Frameworks */, + 3FBE4C262222A3C600782647 /* Sparkle in Frameworks */, 3F15629F23FEDD0000CD0773 /* CoreGeometry in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -279,7 +263,6 @@ 3F7291EC2025BD1E005C9B70 /* Frameworks */ = { isa = PBXGroup; children = ( - 3FBE4C202222A22200782647 /* Sparkle.framework */, ); name = Frameworks; sourceTree = ""; @@ -347,7 +330,6 @@ 3FCD169E1D79793600C57B22 /* Frameworks */, 3FCD169F1D79793600C57B22 /* Resources */, 3FD11B2F207A320800742415 /* Run Script */, - 3FBE4C252222A25200782647 /* Embed Frameworks */, ); buildRules = ( ); @@ -358,6 +340,7 @@ 3F16ECDC23E9D1AC008BC89A /* DefaultsWrapper */, 3F15629E23FEDD0000CD0773 /* CoreGeometry */, 3FE7B6DA245DF8450027DB39 /* SmoothOperators */, + 3FBE4C302222A3C600782647 /* Sparkle */, ); productName = Fluor; productReference = 3FCD16A11D79793600C57B22 /* Fluor.app */; @@ -403,6 +386,7 @@ 3F16ECDB23E9D1AC008BC89A /* XCRemoteSwiftPackageReference "DefaultsWrapper" */, 3F15629D23FEDD0000CD0773 /* XCRemoteSwiftPackageReference "CoreGeometry" */, 3FE7B6D9245DF8450027DB39 /* XCRemoteSwiftPackageReference "SmoothOperators" */, + 3FBE4C2F2222A3C600782647 /* XCRemoteSwiftPackageReference "Sparkle" */, ); productRefGroup = 3FCD16A21D79793600C57B22 /* Products */; projectDirPath = ""; @@ -626,7 +610,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.12; + MACOSX_DEPLOYMENT_TARGET = 10.13; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; @@ -680,7 +664,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.12; + MACOSX_DEPLOYMENT_TARGET = 10.13; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; @@ -699,18 +683,15 @@ CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 5; - DEVELOPMENT_TEAM = Q2E884V952; + DEVELOPMENT_TEAM = 87NLB3L2H3; ENABLE_HARDENED_RUNTIME = YES; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Fluor", - ); + FRAMEWORK_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = Fluor/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 10.12; + MACOSX_DEPLOYMENT_TARGET = 10.13; MARKETING_VERSION = 2.5.0; PRODUCT_BUNDLE_IDENTIFIER = com.pyrolyse.Fluor; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -733,18 +714,15 @@ CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 5; - DEVELOPMENT_TEAM = Q2E884V952; + DEVELOPMENT_TEAM = 87NLB3L2H3; ENABLE_HARDENED_RUNTIME = YES; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Fluor", - ); + FRAMEWORK_SEARCH_PATHS = "$(inherited)"; INFOPLIST_FILE = Fluor/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 10.12; + MACOSX_DEPLOYMENT_TARGET = 10.13; MARKETING_VERSION = 2.5.0; OTHER_SWIFT_FLAGS = "-DRELEASE"; PRODUCT_BUNDLE_IDENTIFIER = com.pyrolyse.Fluor; @@ -798,6 +776,14 @@ minimumVersion = 1.1.0; }; }; + 3FBE4C2F2222A3C600782647 /* XCRemoteSwiftPackageReference "Sparkle" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/sparkle-project/Sparkle.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 2.0.0; + }; + }; 3FE7B6D9245DF8450027DB39 /* XCRemoteSwiftPackageReference "SmoothOperators" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/Pyroh/SmoothOperators.git"; @@ -819,6 +805,11 @@ package = 3F16ECDB23E9D1AC008BC89A /* XCRemoteSwiftPackageReference "DefaultsWrapper" */; productName = DefaultsWrapper; }; + 3FBE4C302222A3C600782647 /* Sparkle */ = { + isa = XCSwiftPackageProductDependency; + package = 3FBE4C2F2222A3C600782647 /* XCRemoteSwiftPackageReference "Sparkle" */; + productName = Sparkle; + }; 3FE7B6DA245DF8450027DB39 /* SmoothOperators */ = { isa = XCSwiftPackageProductDependency; package = 3FE7B6D9245DF8450027DB39 /* XCRemoteSwiftPackageReference "SmoothOperators" */; diff --git a/Fluor.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Fluor.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index d2f1425..84a7c85 100644 --- a/Fluor.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Fluor.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,34 +1,42 @@ { - "object": { - "pins": [ - { - "package": "CoreGeometry", - "repositoryURL": "https://gitlab.com/Pyroh/CoreGeometry.git", - "state": { - "branch": null, - "revision": "a59affa331972263980f8a2af4cfd5f37ad42d3d", - "version": "3.0.0" - } - }, - { - "package": "DefaultsWrapper", - "repositoryURL": "https://github.com/Pyroh/DefaultsWrapper.git", - "state": { - "branch": null, - "revision": "649794e5c7af45f69d1e128a3c9fd0fe1cde282e", - "version": "1.2.0" - } - }, - { - "package": "SmoothOperators", - "repositoryURL": "https://github.com/Pyroh/SmoothOperators.git", - "state": { - "branch": null, - "revision": "12f0d7cc9ade6bc826a822f9d2021b4dd394c4a0", - "version": "0.4.0" - } + "originHash" : "df83e47aff8db5afda649e64a1bb32af88bfc9a2ea94edfe3589bf4f1478a06f", + "pins" : [ + { + "identity" : "coregeometry", + "kind" : "remoteSourceControl", + "location" : "https://gitlab.com/Pyroh/CoreGeometry.git", + "state" : { + "revision" : "a59affa331972263980f8a2af4cfd5f37ad42d3d", + "version" : "3.0.0" } - ] - }, - "version": 1 + }, + { + "identity" : "defaultswrapper", + "kind" : "remoteSourceControl", + "location" : "https://github.com/Pyroh/DefaultsWrapper.git", + "state" : { + "revision" : "649794e5c7af45f69d1e128a3c9fd0fe1cde282e", + "version" : "1.2.0" + } + }, + { + "identity" : "smoothoperators", + "kind" : "remoteSourceControl", + "location" : "https://github.com/Pyroh/SmoothOperators.git", + "state" : { + "revision" : "12f0d7cc9ade6bc826a822f9d2021b4dd394c4a0", + "version" : "0.4.0" + } + }, + { + "identity" : "sparkle", + "kind" : "remoteSourceControl", + "location" : "https://github.com/sparkle-project/Sparkle.git", + "state" : { + "revision" : "21d8df80440b1ca3b65fa82e40782f1e5a9e6ba2", + "version" : "2.9.0" + } + } + ], + "version" : 3 } diff --git a/Fluor.xcodeproj/xcshareddata/xcschemes/Fluor.xcscheme b/Fluor.xcodeproj/xcshareddata/xcschemes/Fluor.xcscheme index bf0f284..5190496 100644 --- a/Fluor.xcodeproj/xcshareddata/xcschemes/Fluor.xcscheme +++ b/Fluor.xcodeproj/xcshareddata/xcschemes/Fluor.xcscheme @@ -5,24 +5,6 @@ - - - - - - - - - - - - - - - - - - - - diff --git a/Fluor/Base.lproj/Preferences.storyboard b/Fluor/Base.lproj/Preferences.storyboard index 99a6830..16b959b 100644 --- a/Fluor/Base.lproj/Preferences.storyboard +++ b/Fluor/Base.lproj/Preferences.storyboard @@ -220,7 +220,7 @@ - + @@ -532,7 +532,7 @@ - + diff --git a/Fluor/Info.plist b/Fluor/Info.plist index 5a8ab56..74a10d9 100644 --- a/Fluor/Info.plist +++ b/Fluor/Info.plist @@ -19,7 +19,7 @@ CFBundleShortVersionString $(MARKETING_VERSION) CFBundleVersion - 2345 + 2347 FLGithubURL https://github.com/Pyroh/Fluor FLPyrolyseURL From b6fa8b6eb655a4cfbb385a30b9a12f98e77f5628 Mon Sep 17 00:00:00 2001 From: Jason Rhubottom Date: Fri, 13 Mar 2026 22:03:56 -0700 Subject: [PATCH 2/8] Add project guidance with build commands and architecture overview --- CLAUDE.md | 65 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/CLAUDE.md b/CLAUDE.md index 55aebc6..47c7e28 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,3 +1,68 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Build Commands + +```bash +# Build (requires Xcode; use DEVELOPER_DIR if xcode-select points to CommandLineTools) +DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer xcodebuild -scheme Fluor -configuration Debug build + +# Build without code signing (for development without matching certificates) +DEVELOPER_DIR=/Applications/Xcode.app/Contents/Developer xcodebuild -scheme Fluor -configuration Debug build CODE_SIGN_IDENTITY="-" CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO +``` + +There are no tests in this project. The project uses Xcode (not Swift Package Manager) as its build system. + +## Architecture + +Fluor is a macOS status bar app (Swift 5 / AppKit) that switches the keyboard's fn-key behavior (media keys vs F1-F12) based on the active application. + +### Core Flow + +``` +AppDelegate → StatusMenuController (status bar item, Main.xib) + ├── BehaviorController — monitors active app, switches fn-key mode via FKeyManager + ├── MenuItemsController — manages menu UI (embeds 3 child ViewControllers) + └── Window Controllers — lazy-loaded via StoryboardInstantiable protocol + ├── PreferencesWindowController (Preferences.storyboard) + ├── RulesEditorWindowController (RulesEditor.storyboard) + ├── RunningAppWindowController (RunningApps.storyboard) + └── AboutWindowController (About.storyboard) +``` + +### Key Components + +- **FKeyManager** (`Misc/FKeyManager.swift`) — IOKit interface that reads/writes the fn-key mode via `IOHIDSetCFTypeParameter` and `IORegistryEntryCreateCFProperty`. Requires Accessibility permissions. +- **BehaviorController** (`Controllers/BehaviorController.swift`) — Core logic: listens to NSWorkspace active-app changes, determines target FKeyMode from stored rules, and calls FKeyManager. Also handles Fn-key press detection for hybrid/key switch methods. +- **AppManager** (`Models/AppManager.swift`) — Singleton storing all persistent state via `@Defaults` property wrappers (from DefaultsWrapper SPM package). Holds the rule set, default mode, switch method, UI preferences. +- **StatusMenuController** (`Controllers/StatusMenuController.swift`) — Owns the NSStatusItem, delegates to BehaviorController and MenuItemsController, manages window controller lifecycle. + +### Communication Pattern + +Components communicate via paired Notification observer/poster protocols defined in `Protocols/NotificationHelpers.swift`: +- `BehaviorDidChange` — fn-key behavior changed for an app +- `SwitchMethodDidChange` — user changed switch method (window/hybrid/key) +- `MenuControlObserver/Poster` — menu open/close coordination + +### Three Switch Methods (enum `SwitchMethod`) + +1. **Window** — auto-switch based on frontmost app's stored rule +2. **Hybrid** — window mode + Fn-key press toggles current app's behavior +3. **Key** — Fn-key press toggles global default mode only + +### Objective-C Interop + +Bridged via `Fluor-Bridging-Header.h`: +- **LaunchAtLoginController** — manages login item registration +- **PFMoveApplication** — prompts to move app to /Applications (RELEASE builds only) + +### SPM Dependencies + +- **DefaultsWrapper** — `@Defaults` property wrapper for typed UserDefaults access +- **Sparkle** (v2.x) — auto-update framework (`SPUStandardUpdaterController` in Preferences.storyboard) +- **CoreGeometry** / **SmoothOperators** — geometry utilities and operator extensions + ## Commit Guidelines - Do not include "Co-Authored-By" lines or any Claude/AI attribution in commit messages. From e0ea6a5dc5c329c747a2a4cdaa9694d1875c0aba Mon Sep 17 00:00:00 2001 From: Jason Rhubottom Date: Fri, 13 Mar 2026 23:36:42 -0700 Subject: [PATCH 3/8] Fix EXC_BAD_ACCESS crash when opening Rules Editor and Running Apps windows MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The Rules Editor crash was caused by a toolTip binding using the nested key path `objectValue.url.path`. The `url` property on Item is declared as `@objc let`, which doesn't expose a mutable ivar to the ObjC runtime. When KVO set up auto-notifying for this nested path, NSKeyValueIvarSetter received a nil ivar and dereferenced it (address 0x0). Also removed the unused selectionIndexes binding on the table view, changed both windows from NSPanel to NSWindow (customClass removal), and updated editRules/showRunningApps to use makeKeyAndOrderFront with NSApp.activate — matching the working pattern from showAbout/showPreferences. --- Fluor/Base.lproj/RulesEditor.storyboard | 6 +----- Fluor/Base.lproj/RunningApps.storyboard | 2 +- Fluor/Controllers/StatusMenuController.swift | 16 ++++++++++++---- 3 files changed, 14 insertions(+), 10 deletions(-) diff --git a/Fluor/Base.lproj/RulesEditor.storyboard b/Fluor/Base.lproj/RulesEditor.storyboard index 25508e5..87e3e12 100644 --- a/Fluor/Base.lproj/RulesEditor.storyboard +++ b/Fluor/Base.lproj/RulesEditor.storyboard @@ -10,7 +10,7 @@ - + @@ -104,7 +104,6 @@ - @@ -173,9 +172,6 @@ - - - diff --git a/Fluor/Base.lproj/RunningApps.storyboard b/Fluor/Base.lproj/RunningApps.storyboard index 71c4f5c..a086447 100644 --- a/Fluor/Base.lproj/RunningApps.storyboard +++ b/Fluor/Base.lproj/RunningApps.storyboard @@ -10,7 +10,7 @@ - + diff --git a/Fluor/Controllers/StatusMenuController.swift b/Fluor/Controllers/StatusMenuController.swift index 8a1a3d0..d059ec9 100644 --- a/Fluor/Controllers/StatusMenuController.swift +++ b/Fluor/Controllers/StatusMenuController.swift @@ -137,12 +137,16 @@ class StatusMenuController: NSObject, NSMenuDelegate, NSWindowDelegate, MenuCont /// - parameter sender: The object that sent the action. @IBAction func editRules(_ sender: AnyObject) { guard rulesController == nil else { - rulesController?.window?.orderFrontRegardless() + rulesController?.window?.makeKeyAndOrderFront(self) + rulesController?.window?.makeMain() + NSApp.activate(ignoringOtherApps: true) return } rulesController = RulesEditorWindowController.instantiate() rulesController?.window?.delegate = self - rulesController?.window?.orderFrontRegardless() + rulesController?.window?.makeKeyAndOrderFront(self) + rulesController?.window?.makeMain() + NSApp.activate(ignoringOtherApps: true) } /// Show the *About* window. @@ -184,12 +188,16 @@ class StatusMenuController: NSObject, NSMenuDelegate, NSWindowDelegate, MenuCont /// - parameter sender: The object that sent the action. @IBAction func showRunningApps(_ sender: AnyObject) { guard runningAppsController == nil else { - runningAppsController?.window?.orderFrontRegardless() + runningAppsController?.window?.makeKeyAndOrderFront(self) + runningAppsController?.window?.makeMain() + NSApp.activate(ignoringOtherApps: true) return } runningAppsController = RunningAppWindowController.instantiate() runningAppsController?.window?.delegate = self - runningAppsController?.window?.orderFrontRegardless() + runningAppsController?.window?.makeKeyAndOrderFront(self) + runningAppsController?.window?.makeMain() + NSApp.activate(ignoringOtherApps: true) } From 1ad1b59aea90be619180699100a245d48ae87644 Mon Sep 17 00:00:00 2001 From: Jason Rhubottom Date: Fri, 13 Mar 2026 23:37:01 -0700 Subject: [PATCH 4/8] Update Sparkle bindings for v2 API and bump build number Migrate Preferences.storyboard Sparkle bindings to use the v2 key paths (self.updater.automaticallyChecksForUpdates, etc.) and remove the unused SPUStandardUpdaterController custom object. Bump CFBundleVersion to 2371 and downgrade scheme version to 1.3 for broader Xcode compatibility. --- Fluor.xcodeproj/xcshareddata/xcschemes/Fluor.xcscheme | 2 +- Fluor/Base.lproj/Preferences.storyboard | 7 +++---- Fluor/Info.plist | 2 +- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/Fluor.xcodeproj/xcshareddata/xcschemes/Fluor.xcscheme b/Fluor.xcodeproj/xcshareddata/xcschemes/Fluor.xcscheme index 5190496..591742c 100644 --- a/Fluor.xcodeproj/xcshareddata/xcschemes/Fluor.xcscheme +++ b/Fluor.xcodeproj/xcshareddata/xcschemes/Fluor.xcscheme @@ -1,7 +1,7 @@ + version = "1.3"> diff --git a/Fluor/Base.lproj/Preferences.storyboard b/Fluor/Base.lproj/Preferences.storyboard index 16b959b..0093775 100644 --- a/Fluor/Base.lproj/Preferences.storyboard +++ b/Fluor/Base.lproj/Preferences.storyboard @@ -220,7 +220,6 @@ - @@ -420,7 +419,7 @@ - + @@ -433,7 +432,7 @@ - + @@ -470,7 +469,7 @@ - + diff --git a/Fluor/Info.plist b/Fluor/Info.plist index 74a10d9..74d318a 100644 --- a/Fluor/Info.plist +++ b/Fluor/Info.plist @@ -19,7 +19,7 @@ CFBundleShortVersionString $(MARKETING_VERSION) CFBundleVersion - 2347 + 2371 FLGithubURL https://github.com/Pyroh/Fluor FLPyrolyseURL From 3f8392a302f5e07baccc8dfa020d50b16ac6e2c4 Mon Sep 17 00:00:00 2001 From: Jason Rhubottom Date: Fri, 13 Mar 2026 23:42:26 -0700 Subject: [PATCH 5/8] Fix Rules Editor remove button by binding table selection to array controller The "-" button in the Rules Editor was permanently disabled because the table view's selectionIndexes were not bound to the NSArrayController. Without this binding, canRemove stayed false and selectedObjects was always empty. Also updates CLAUDE.md with gotchas and bumps build number. --- CLAUDE.md | 8 +++++++- .../ViewControllers/RulesEditorViewController.swift | 5 ++++- Fluor/Info.plist | 2 +- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 47c7e28..c5efb69 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -60,9 +60,15 @@ Bridged via `Fluor-Bridging-Header.h`: ### SPM Dependencies - **DefaultsWrapper** — `@Defaults` property wrapper for typed UserDefaults access -- **Sparkle** (v2.x) — auto-update framework (`SPUStandardUpdaterController` in Preferences.storyboard) +- **Sparkle** (v2.x) — auto-update framework (bindings go through `self.updater.*` key paths in Preferences.storyboard) - **CoreGeometry** / **SmoothOperators** — geometry utilities and operator extensions +## Gotchas + +- **KVO + `@objc let` properties**: Storyboard bindings using nested key paths through `@objc let` stored properties (e.g., `objectValue.url.path` where `url` is `let`) crash on modern macOS — KVO can't create an ivar setter for constants. Use a `@objc dynamic var` computed wrapper instead, or avoid nested paths through `let` properties. +- **Window display pattern**: Use `makeKeyAndOrderFront(self)` + `makeMain()` + `NSApp.activate(ignoringOtherApps:)` to show windows. Do not use `orderFrontRegardless()`. +- **Storyboard debugging**: When investigating storyboard-related crashes, always request the crash backtrace — code review alone is insufficient for KVO/binding issues. + ## Commit Guidelines - Do not include "Co-Authored-By" lines or any Claude/AI attribution in commit messages. diff --git a/Fluor/Controllers/ViewControllers/RulesEditorViewController.swift b/Fluor/Controllers/ViewControllers/RulesEditorViewController.swift index 6d3300b..21e7bfc 100644 --- a/Fluor/Controllers/ViewControllers/RulesEditorViewController.swift +++ b/Fluor/Controllers/ViewControllers/RulesEditorViewController.swift @@ -50,10 +50,13 @@ class RulesEditorViewController: NSViewController, BehaviorDidChangeObserver { self.rulesSet = AppManager.default.rules + tableView.bind(.selectionIndexes, to: itemsArrayController!, withKeyPath: "selectionIndexes", options: nil) + self.tableContentAnimator = TableViewContentAnimator(tableView: tableView, arrayController: itemsArrayController) } - + deinit { + tableView.unbind(.selectionIndexes) stopObservingBehaviorDidChange() itemsArrayController.removeObserver(self, forKeyPath: "canRemove") itemsArrayController.removeObserver(self, forKeyPath: "canAdd") diff --git a/Fluor/Info.plist b/Fluor/Info.plist index 74d318a..8c2e0a3 100644 --- a/Fluor/Info.plist +++ b/Fluor/Info.plist @@ -19,7 +19,7 @@ CFBundleShortVersionString $(MARKETING_VERSION) CFBundleVersion - 2371 + 2373 FLGithubURL https://github.com/Pyroh/Fluor FLPyrolyseURL From 0c2c85794fd7e3c81fdedf4d68173089668ab029 Mon Sep 17 00:00:00 2001 From: Jason Rhubottom Date: Fri, 13 Mar 2026 23:44:59 -0700 Subject: [PATCH 6/8] Update SPM Package.resolved to v3 format --- .../xcshareddata/swiftpm/Package.resolved | 61 +++++++++---------- 1 file changed, 30 insertions(+), 31 deletions(-) diff --git a/Fluor.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Fluor.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index d2f1425..1630493 100644 --- a/Fluor.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Fluor.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,34 +1,33 @@ { - "object": { - "pins": [ - { - "package": "CoreGeometry", - "repositoryURL": "https://gitlab.com/Pyroh/CoreGeometry.git", - "state": { - "branch": null, - "revision": "a59affa331972263980f8a2af4cfd5f37ad42d3d", - "version": "3.0.0" - } - }, - { - "package": "DefaultsWrapper", - "repositoryURL": "https://github.com/Pyroh/DefaultsWrapper.git", - "state": { - "branch": null, - "revision": "649794e5c7af45f69d1e128a3c9fd0fe1cde282e", - "version": "1.2.0" - } - }, - { - "package": "SmoothOperators", - "repositoryURL": "https://github.com/Pyroh/SmoothOperators.git", - "state": { - "branch": null, - "revision": "12f0d7cc9ade6bc826a822f9d2021b4dd394c4a0", - "version": "0.4.0" - } + "originHash" : "9909bd4fc9f9e45ce69ffb584bbdae406c0443cc9c25d1bfec2bed3fe9bdb5e9", + "pins" : [ + { + "identity" : "coregeometry", + "kind" : "remoteSourceControl", + "location" : "https://gitlab.com/Pyroh/CoreGeometry.git", + "state" : { + "revision" : "a59affa331972263980f8a2af4cfd5f37ad42d3d", + "version" : "3.0.0" } - ] - }, - "version": 1 + }, + { + "identity" : "defaultswrapper", + "kind" : "remoteSourceControl", + "location" : "https://github.com/Pyroh/DefaultsWrapper.git", + "state" : { + "revision" : "649794e5c7af45f69d1e128a3c9fd0fe1cde282e", + "version" : "1.2.0" + } + }, + { + "identity" : "smoothoperators", + "kind" : "remoteSourceControl", + "location" : "https://github.com/Pyroh/SmoothOperators.git", + "state" : { + "revision" : "12f0d7cc9ade6bc826a822f9d2021b4dd394c4a0", + "version" : "0.4.0" + } + } + ], + "version" : 3 } From 0c09e92395c4818645be1d7f700dc2a05ff299e2 Mon Sep 17 00:00:00 2001 From: Jason Rhubottom Date: Fri, 13 Mar 2026 23:45:22 -0700 Subject: [PATCH 7/8] Updated gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index a5dc678..e49e0f6 100644 --- a/.gitignore +++ b/.gitignore @@ -63,3 +63,4 @@ fastlane/screenshots Documentation/docs /Fluor/Sparkle.framework /Fluor/Sparkle.framework.dSYM +.DS_Store From 781e1c37b263d540ea5672cf318af54b479f7580 Mon Sep 17 00:00:00 2001 From: Jason Rhubottom Date: Fri, 13 Mar 2026 23:51:39 -0700 Subject: [PATCH 8/8] Build Number --- Fluor/Info.plist | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Fluor/Info.plist b/Fluor/Info.plist index 8c2e0a3..8396660 100644 --- a/Fluor/Info.plist +++ b/Fluor/Info.plist @@ -19,7 +19,7 @@ CFBundleShortVersionString $(MARKETING_VERSION) CFBundleVersion - 2373 + 2374 FLGithubURL https://github.com/Pyroh/Fluor FLPyrolyseURL