From c2be71344633b2e04ea419028f8c05e75df1d5e9 Mon Sep 17 00:00:00 2001 From: James Maes Date: Sun, 8 Nov 2026 17:27:40 -0600 Subject: [PATCH 1/5] ci: build on the shared ci-workflows macOS workflow (build only) Gitea CI on grogu (kof-macos-arm64): Guards B and C from guard.yml (the de-paywall marker and if-false wrap in computeState(), no conflict markers), an unsigned Release build, and build-for-testing of the Test scheme. The tests (Guard A) are not run on CI: the runner has no GUI session for testmanagerd; docs/contributing.md documents running scripts/run_tests.sh locally before merging. config/test-base.xcconfig takes the unit-test-only Xcode 27 fix from PR #1 (test target 12.0, deprecation group downgraded, every other warning still an error); the shipping app keeps 10.13. The .github workflows stay for the GitHub mirror. Co-Authored-By: Claude Opus 5.5 --- .gitea/workflows/build.yaml | 28 ++++++++++++++++++++++++++++ config/test-base.xcconfig | 7 +++++-- docs/contributing.md | 6 ++++++ 3 files changed, 39 insertions(+), 2 deletions(-) create mode 100644 .gitea/workflows/build.yaml diff --git a/.gitea/workflows/build.yaml b/.gitea/workflows/build.yaml new file mode 100644 index 00000000..27f3ffa7 --- /dev/null +++ b/.gitea/workflows/build.yaml @@ -0,0 +1,28 @@ +# Gitea CI on the shared KofTwentyTwo/ci-workflows macOS workflow (grogu). The .github workflows keep +# running on the GitHub mirror (release, notarization, appcast); this one is build only and unsigned. +# +# Guard B the de-paywall marker and the if-false wrap are still in computeState() (docs/PLAN-maintained-fork.md §4.4) +# Guard C no unresolved merge conflict markers in the tree +# build the Release scheme, unsigned +# Guard A the Test scheme is compiled (build-for-testing) but not run: the grogu runner has no GUI session for +# testmanagerd. Run scripts/run_tests.sh locally before merging (docs/contributing.md). +name: build +on: + push: + branches: ['**'] +jobs: + macos: + uses: self:KofTwentyTwo/ci-workflows/.gitea/workflows/macos.yaml@v1 + with: + commands: | + F=src/pro/license/LicenseManager.swift + [ -f "$F" ] || { echo "::error::Guard B: $F not found; chokepoint moved? Refresh the de-paywall patch (§6.2)."; exit 1; } + grep -q 'depaywall' "$F" || { echo "::error::PAYWALL RELOCKED: 'depaywall' marker missing from $F (§6.2)."; exit 1; } + grep -Eq 'guard[[:space:]]+false[[:space:]]+else' "$F" || { echo "::error::PAYWALL RELOCKED: 'if false { … }' wrap missing from $F (§6.2)."; exit 1; } + echo "Guard B OK" + if git -c grep.lineNumber=false grep -nE '^(<<<<<<<|=======|>>>>>>>)' -- . ':(exclude)docs/'; then + echo "::error::UNRESOLVED CONFLICT MARKERS present; resolve before merge (§4.4 Guard C)."; exit 1 + fi + echo "Guard C OK" + xcodebuild -quiet -project alt-tab-macos.xcodeproj -scheme Release -derivedDataPath DerivedData CODE_SIGN_IDENTITY=- CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO build + xcodebuild -quiet -project alt-tab-macos.xcodeproj -scheme Test -configuration Release -derivedDataPath DerivedData build-for-testing diff --git a/config/test-base.xcconfig b/config/test-base.xcconfig index 985ae51e..5d24ca7c 100644 --- a/config/test-base.xcconfig +++ b/config/test-base.xcconfig @@ -1,8 +1,11 @@ // docs: https://help.apple.com/xcode/mac/11.4/#/itcaec37c2a6 SWIFT_VERSION = 5.8 -SWIFT_TREAT_WARNINGS_AS_ERRORS = YES -MACOSX_DEPLOYMENT_TARGET = 10.13 +SWIFT_TREAT_WARNINGS_AS_ERRORS = NO // replaced by the explicit flags below so the deprecation group can be downgraded after them +// Raising the unit-test target to 12.0 turns uses of APIs deprecated in macOS 11/12 (kept for the 10.13 app) into warnings; only that +// group is downgraded here (after -warnings-as-errors, which would otherwise win); every other warning stays an error. +OTHER_SWIFT_FLAGS = $(inherited) -warnings-as-errors -Wwarning DeprecatedDeclaration +MACOSX_DEPLOYMENT_TARGET = 12.0 // unit-tests only: Xcode 27 supports 12.0+; the shipping app keeps 10.13 in base.xcconfig CODE_SIGNING_REQUIRED = NO // no need for codesign CODE_SIGNING_ALLOWED = NO // no need for codesign ONLY_ACTIVE_ARCH = YES // only need to run on the host machine diff --git a/docs/contributing.md b/docs/contributing.md index a0c60250..7091128b 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -11,6 +11,12 @@ This project has minimal dependency on Xcode-only features (e.g. InterfaceBuilde * `scripts/codesign/setup_local.sh` to generate a local self-signed certificate, to avoid having to re-check the `System Preferences > Security & Privacy` permissions on every build * Either open `alt-tab-macos.xcodeproj` with Xcode, or use the CLI: `xcodebuild -project alt-tab-macos.xcodeproj -scheme Debug` to build the .app with the `Debug` build configuration +### Tests (run locally before merging) + +The Gitea CI (`.gitea/workflows/build.yaml`) runs Guards B and C (the de-paywall markers and conflict markers), builds the `Release` scheme unsigned and compiles the `Test` scheme (`build-for-testing`), but does not run the tests: its macOS runner has no GUI session, so `xcodebuild test` cannot reach `testmanagerd`. Run them, including the anti-relock tests in `src/pro/license/LicenseManagerTests.swift` (Guard A, `docs/PLAN-maintained-fork.md` §4.4), locally before merging, especially an upstream sync: + +* `scripts/run_tests.sh` (the `Test` scheme, `-configuration Release`) + ## Mac development Mac development ecosystem is pretty terrible in general. They keep piling on the tech stacks on top of each other, so you have C APIs, ObjC APIs, Swift APIs, Interface builder, Playgrounds, Swift UI, Mac Catalyst. All these are bridging with each other with a bunch of macros, SDKs glue, compiler flags, compatibility mode, XCode legacy build system, etc. For alt-tab, we are on Swift 5.0. Note that swift just recently started being stable, but overall any change of version breaks a lot of stuff. Swift itself is the mainstream language with the worst governance I’ve seen in modern times. -- 2.54.0 From c675f1817fd16191d3e8296342c6b56ac1eef52e Mon Sep 17 00:00:00 2001 From: James Maes Date: Sun, 8 Nov 2026 17:28:37 -0600 Subject: [PATCH 2/5] ci: select Xcode 26.0.1, as ci_cd.yml does Xcode 27.2 rejects the app's macOS 10.13 deployment target. Co-Authored-By: Claude Opus 5.5 --- .gitea/workflows/build.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitea/workflows/build.yaml b/.gitea/workflows/build.yaml index 27f3ffa7..a9aba145 100644 --- a/.gitea/workflows/build.yaml +++ b/.gitea/workflows/build.yaml @@ -14,6 +14,8 @@ jobs: macos: uses: self:KofTwentyTwo/ci-workflows/.gitea/workflows/macos.yaml@v1 with: + # Same Xcode as .github/workflows/ci_cd.yml: Xcode 27 no longer accepts the app's 10.13 deployment target. + xcode: '26.0.1' commands: | F=src/pro/license/LicenseManager.swift [ -f "$F" ] || { echo "::error::Guard B: $F not found; chokepoint moved? Refresh the de-paywall patch (§6.2)."; exit 1; } -- 2.54.0 From 756943f2b5c496c270b3325a556d2f9533aa5d90 Mon Sep 17 00:00:00 2001 From: James Maes Date: Sun, 8 Nov 2026 17:29:44 -0600 Subject: [PATCH 3/5] ci: compile the Release scheme at macOS 12.0 on Xcode 27 grogu has only Xcode 27.2, which rejects the 10.13 deployment target. The CI build is a compile check at 12.0 with the deprecation group downgraded (as config/test-base.xcconfig); the shipping build stays on the GitHub mirror with Xcode 26.0.1. Co-Authored-By: Claude Opus 5.5 --- .gitea/workflows/build.yaml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.gitea/workflows/build.yaml b/.gitea/workflows/build.yaml index a9aba145..5930d251 100644 --- a/.gitea/workflows/build.yaml +++ b/.gitea/workflows/build.yaml @@ -3,7 +3,7 @@ # # Guard B the de-paywall marker and the if-false wrap are still in computeState() (docs/PLAN-maintained-fork.md §4.4) # Guard C no unresolved merge conflict markers in the tree -# build the Release scheme, unsigned +# build the Release scheme, unsigned (a compile check at macOS 12.0: grogu has only Xcode 27) # Guard A the Test scheme is compiled (build-for-testing) but not run: the grogu runner has no GUI session for # testmanagerd. Run scripts/run_tests.sh locally before merging (docs/contributing.md). name: build @@ -14,8 +14,6 @@ jobs: macos: uses: self:KofTwentyTwo/ci-workflows/.gitea/workflows/macos.yaml@v1 with: - # Same Xcode as .github/workflows/ci_cd.yml: Xcode 27 no longer accepts the app's 10.13 deployment target. - xcode: '26.0.1' commands: | F=src/pro/license/LicenseManager.swift [ -f "$F" ] || { echo "::error::Guard B: $F not found; chokepoint moved? Refresh the de-paywall patch (§6.2)."; exit 1; } @@ -26,5 +24,8 @@ jobs: echo "::error::UNRESOLVED CONFLICT MARKERS present; resolve before merge (§4.4 Guard C)."; exit 1 fi echo "Guard C OK" - xcodebuild -quiet -project alt-tab-macos.xcodeproj -scheme Release -derivedDataPath DerivedData CODE_SIGN_IDENTITY=- CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO build + # grogu has only Xcode 27.2, which rejects the app's 10.13 deployment target (the shipping build is the GitHub + # mirror's, on Xcode 26.0.1). This compile check builds at 12.0 with the same deprecation downgrade as + # config/test-base.xcconfig; every other Swift warning stays an error. + xcodebuild -quiet -project alt-tab-macos.xcodeproj -scheme Release -derivedDataPath DerivedData CODE_SIGN_IDENTITY=- CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO \n MACOSX_DEPLOYMENT_TARGET=12.0 SWIFT_TREAT_WARNINGS_AS_ERRORS=NO 'OTHER_SWIFT_FLAGS=$(inherited) -warnings-as-errors -Wwarning DeprecatedDeclaration' build xcodebuild -quiet -project alt-tab-macos.xcodeproj -scheme Test -configuration Release -derivedDataPath DerivedData build-for-testing -- 2.54.0 From 874971aa478eb5256be5858a2ba99a61414693f1 Mon Sep 17 00:00:00 2001 From: James Maes Date: Sun, 8 Nov 2026 17:30:44 -0600 Subject: [PATCH 4/5] ci: one-line Release build command Co-Authored-By: Claude Opus 5.5 --- .gitea/workflows/build.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitea/workflows/build.yaml b/.gitea/workflows/build.yaml index 5930d251..29c819f1 100644 --- a/.gitea/workflows/build.yaml +++ b/.gitea/workflows/build.yaml @@ -27,5 +27,5 @@ jobs: # grogu has only Xcode 27.2, which rejects the app's 10.13 deployment target (the shipping build is the GitHub # mirror's, on Xcode 26.0.1). This compile check builds at 12.0 with the same deprecation downgrade as # config/test-base.xcconfig; every other Swift warning stays an error. - xcodebuild -quiet -project alt-tab-macos.xcodeproj -scheme Release -derivedDataPath DerivedData CODE_SIGN_IDENTITY=- CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO \n MACOSX_DEPLOYMENT_TARGET=12.0 SWIFT_TREAT_WARNINGS_AS_ERRORS=NO 'OTHER_SWIFT_FLAGS=$(inherited) -warnings-as-errors -Wwarning DeprecatedDeclaration' build + xcodebuild -quiet -project alt-tab-macos.xcodeproj -scheme Release -derivedDataPath DerivedData CODE_SIGN_IDENTITY=- CODE_SIGNING_REQUIRED=NO CODE_SIGNING_ALLOWED=NO MACOSX_DEPLOYMENT_TARGET=12.0 SWIFT_TREAT_WARNINGS_AS_ERRORS=NO 'OTHER_SWIFT_FLAGS=$(inherited) -warnings-as-errors -Wwarning DeprecatedDeclaration' build xcodebuild -quiet -project alt-tab-macos.xcodeproj -scheme Test -configuration Release -derivedDataPath DerivedData build-for-testing -- 2.54.0 From da5779f872a4466f2cb9149ccef458dbb5a023d7 Mon Sep 17 00:00:00 2001 From: James Maes Date: Sun, 8 Nov 2026 17:32:05 -0600 Subject: [PATCH 5/5] fix: capture weakly in the outer resolve closures (Swift 6.3 ImplicitStrongCapture) The background closures in ExceptionsTab.applyContent and ExceptionEditorView.resolveHeaderAsync captured row/self strongly while the inner main-queue closure asked for weak; Xcode 27's Swift diagnoses the mismatch (#ImplicitStrongCapture), an error under warnings-as-errors. Both closures now capture weakly, so neither keeps the row or view alive while the app info resolves. Co-Authored-By: Claude Opus 5.5 --- src/preferences/settings-window/tabs/ExceptionEditorView.swift | 2 +- src/preferences/settings-window/tabs/ExceptionsTab.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/preferences/settings-window/tabs/ExceptionEditorView.swift b/src/preferences/settings-window/tabs/ExceptionEditorView.swift index 953f9c90..63719ad7 100644 --- a/src/preferences/settings-window/tabs/ExceptionEditorView.swift +++ b/src/preferences/settings-window/tabs/ExceptionEditorView.swift @@ -291,7 +291,7 @@ class ExceptionEditorView: NSView { } private func resolveHeaderAsync(for bundleId: String) { - DispatchQueue.global(qos: .userInitiated).async { + DispatchQueue.global(qos: .userInitiated).async { [weak self] in let info = AppDisplayInfo.resolve(bundleId: bundleId) DispatchQueue.main.async { [weak self] in guard let self, self.entry.bundleIdentifier == bundleId else { return } diff --git a/src/preferences/settings-window/tabs/ExceptionsTab.swift b/src/preferences/settings-window/tabs/ExceptionsTab.swift index 99456631..d0a55bf8 100644 --- a/src/preferences/settings-window/tabs/ExceptionsTab.swift +++ b/src/preferences/settings-window/tabs/ExceptionsTab.swift @@ -319,7 +319,7 @@ class ExceptionsTab { // BundleId changed (or first paint): show a synchronous placeholder, then resolve async. row.setIcon(AppDisplayInfo.genericIcon, size: iconSize) row.setContent(bundleId, summary) - DispatchQueue.global(qos: .userInitiated).async { + DispatchQueue.global(qos: .userInitiated).async { [weak row] in let info = AppDisplayInfo.resolve(bundleId: bundleId) DispatchQueue.main.async { [weak row] in guard let row else { return } -- 2.54.0