From b1e70888e2eeba335f116a792cc5fc2eb4afdaf5 Mon Sep 17 00:00:00 2001 From: Dion McMurtrie Date: Sun, 4 Oct 2026 16:25:17 +1000 Subject: [PATCH 1/4] ci: move to macos-26 / Xcode 26.6 and current action majors - runs-on macos-14 -> macos-26 (macos-14 images are deprecated: actions/runner-images#13518); Xcode 16.2 -> 26.6, the image default. - actions/checkout v4 -> v7, actions/upload-artifact v4 -> v7, softprops/action-gh-release v2 -> v3 (Node 24 runtimes; no input changes affect these workflows). - Pipe xcodebuild through xcbeautify, which ships on the macos-26 image, instead of xcpretty, which does not. - Run ci.yml steps with `shell: bash` so they get `-eo pipefail`. Previously the default `bash -e` meant a failing xcodebuild build or test step piped into the formatter still reported success. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/ci.yml | 42 ++++++++++++++++++++--------------- .github/workflows/release.yml | 8 +++---- 2 files changed, 28 insertions(+), 22 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 821e954..71847cf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,14 +10,20 @@ env: SCHEME: "Codeagogo" DESTINATION: "platform=macOS" +# Explicit bash runs steps with `-eo pipefail`, so an xcodebuild failure is not +# masked by the exit status of the xcbeautify formatter it is piped into. +defaults: + run: + shell: bash + jobs: lint: name: SwiftLint - runs-on: macos-14 + runs-on: macos-26 steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v7 - name: Install SwiftLint run: brew install swiftlint @@ -27,22 +33,22 @@ jobs: audit: name: Dependency Audit - runs-on: macos-14 + runs-on: macos-26 steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v7 - name: Audit Dependencies run: ./scripts/audit-dependencies.sh secrets: name: Secrets Scan - runs-on: macos-14 + runs-on: macos-26 steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v7 with: fetch-depth: 0 @@ -54,26 +60,26 @@ jobs: license-headers: name: License Headers - runs-on: macos-14 + runs-on: macos-26 steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v7 - name: Check license headers run: ./scripts/license-header.sh --check build-and-test: name: Build and Test - runs-on: macos-14 + runs-on: macos-26 needs: [lint, audit, secrets, license-headers] steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v7 - name: Select Xcode - run: sudo xcode-select -s /Applications/Xcode_16.2.app/Contents/Developer + run: sudo xcode-select -s /Applications/Xcode_26.6.app/Contents/Developer - name: Show Xcode version run: xcodebuild -version @@ -88,7 +94,7 @@ jobs: CODE_SIGNING_REQUIRED=NO \ ENABLE_HARDENED_RUNTIME=NO \ ENABLE_APP_SANDBOX=NO \ - | xcpretty + | xcbeautify - name: Unit Tests run: | @@ -101,7 +107,7 @@ jobs: CODE_SIGNING_REQUIRED=NO \ ENABLE_HARDENED_RUNTIME=NO \ ENABLE_APP_SANDBOX=NO \ - | xcpretty + | xcbeautify - name: UI Tests run: | @@ -114,20 +120,20 @@ jobs: CODE_SIGNING_REQUIRED=YES \ ENABLE_HARDENED_RUNTIME=NO \ ENABLE_APP_SANDBOX=NO \ - | xcpretty + | xcbeautify build-artifact: name: Build Release Artifact - runs-on: macos-14 + runs-on: macos-26 needs: build-and-test if: github.event_name == 'push' && github.ref == 'refs/heads/main' steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v7 - name: Select Xcode - run: sudo xcode-select -s /Applications/Xcode_16.2.app/Contents/Developer + run: sudo xcode-select -s /Applications/Xcode_26.6.app/Contents/Developer - name: Install Apple Certificate env: @@ -180,7 +186,7 @@ jobs: zip -r "Codeagogo-macOS.zip" "Codeagogo.app" - name: Upload Artifact - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v7 with: name: Codeagogo-macOS path: artifact/Codeagogo-macOS.zip diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7912402..3271f96 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -15,7 +15,7 @@ env: jobs: release: name: Create Release - runs-on: macos-14 + runs-on: macos-26 permissions: contents: write @@ -28,7 +28,7 @@ jobs: fi - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v7 with: fetch-depth: 0 token: ${{ secrets.GITHUB_TOKEN }} @@ -107,7 +107,7 @@ jobs: git push origin "v${{ inputs.version }}" - name: Select Xcode - run: sudo xcode-select -s /Applications/Xcode_16.2.app/Contents/Developer + run: sudo xcode-select -s /Applications/Xcode_26.6.app/Contents/Developer - name: Install Apple Certificate if: env.APPLE_CERTIFICATE_BASE64 != '' @@ -174,7 +174,7 @@ jobs: zip -r "Codeagogo-v${{ inputs.version }}-macOS.zip" "Codeagogo.app" - name: Create GitHub Release - uses: softprops/action-gh-release@v2 + uses: softprops/action-gh-release@v3 with: tag_name: v${{ inputs.version }} name: Codeagogo v${{ inputs.version }} From 3430e96385a8228fc835d8894bfafe36c946e0f8 Mon Sep 17 00:00:00 2001 From: Dion McMurtrie Date: Mon, 5 Oct 2026 00:09:56 +1000 Subject: [PATCH 2/4] ci: run UI tests from the Codeagogo-UI test plan The UI Tests step used -only-testing:CodeagogoUITests against the scheme's default (Unit) test plan, which doesn't contain that target, so xcodebuild has failed with exit 70 on every run; the formatter pipe hid it until pipefail was enabled. Select the Codeagogo-UI plan (which also keeps the SearchPanelUITests skip), bound the step at 20 minutes in case XCUITest hangs, and select the Unit plan explicitly for the unit step. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/ci.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 71847cf..8154089 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -101,7 +101,7 @@ jobs: xcodebuild test \ -scheme "$SCHEME" \ -destination "$DESTINATION" \ - -only-testing:"CodeagogoTests" \ + -testPlan "Codeagogo-Unit" \ -configuration Debug \ CODE_SIGN_IDENTITY="-" \ CODE_SIGNING_REQUIRED=NO \ @@ -109,12 +109,16 @@ jobs: ENABLE_APP_SANDBOX=NO \ | xcbeautify + # The UI tests live only in the Codeagogo-UI test plan (it also skips the + # SearchPanelUITests suite, which hangs XCUITest). GitHub's macOS runners + # have a logged-in GUI session, which XCUITest needs; no display required. - name: UI Tests + timeout-minutes: 20 run: | xcodebuild test \ -scheme "$SCHEME" \ -destination "$DESTINATION" \ - -only-testing:"CodeagogoUITests" \ + -testPlan "Codeagogo-UI" \ -configuration Debug \ CODE_SIGN_IDENTITY="-" \ CODE_SIGNING_REQUIRED=YES \ From 7a2a756dbe1b1ef45ed0cf7e3a0e3053dca2c283 Mon Sep 17 00:00:00 2001 From: Dion McMurtrie Date: Mon, 5 Oct 2026 00:18:41 +1000 Subject: [PATCH 3/4] test: scroll Settings controls into view before clicking The Settings window is 1060pt tall, taller than the CI runner's screen, so the debug-logging toggle and diagnostics button at the bottom of its scroll view were off-screen and XCUITest failed them as not hittable. Add a scrollIntoView helper to the UI test base class and use it before clicking those controls. Co-Authored-By: Claude Opus 5.5 --- CodeagogoUITests/CodeagogoUITestCase.swift | 17 +++++++++++++++++ CodeagogoUITests/SettingsViewUITests.swift | 2 ++ 2 files changed, 19 insertions(+) diff --git a/CodeagogoUITests/CodeagogoUITestCase.swift b/CodeagogoUITests/CodeagogoUITestCase.swift index 109b459..6621541 100644 --- a/CodeagogoUITests/CodeagogoUITestCase.swift +++ b/CodeagogoUITests/CodeagogoUITestCase.swift @@ -66,4 +66,21 @@ class SNOMEDLookupUITestCase: XCTestCase { } return settingsWindow } + + /// Scrolls the window's scroll view until `element` can be clicked. + /// + /// The Settings window is taller than small screens (such as CI runners), + /// so controls near the bottom of its scroll view start out off-screen and + /// are not hittable until scrolled into view. + /// + /// - Parameters: + /// - element: The element to bring into view. + /// - window: The window containing the scroll view. + func scrollIntoView(_ element: XCUIElement, in window: XCUIElement) { + let scrollView = window.scrollViews.firstMatch + guard scrollView.exists else { return } + for _ in 0..<10 where !element.isHittable { + scrollView.scroll(byDeltaX: 0, deltaY: -200) + } + } } diff --git a/CodeagogoUITests/SettingsViewUITests.swift b/CodeagogoUITests/SettingsViewUITests.swift index 2846374..3f48020 100644 --- a/CodeagogoUITests/SettingsViewUITests.swift +++ b/CodeagogoUITests/SettingsViewUITests.swift @@ -188,6 +188,7 @@ final class SettingsViewUITests: SNOMEDLookupUITestCase { XCTFail("Debug logging toggle not found") return } + scrollIntoView(toggle, in: settingsWindow) let initialValue = toggle.value as? Int ?? 0 toggle.click() let newValue = toggle.value as? Int ?? 0 @@ -212,6 +213,7 @@ final class SettingsViewUITests: SNOMEDLookupUITestCase { return } XCTAssertTrue(button.isEnabled, "Diagnostics button should be enabled") + scrollIntoView(button, in: settingsWindow) button.click() // Button should still exist after clicking XCTAssertTrue(button.exists, From 48435d30accc8b4e8cf90d9b851aba08c7b8a65e Mon Sep 17 00:00:00 2001 From: Dion McMurtrie Date: Mon, 5 Oct 2026 22:30:51 +1000 Subject: [PATCH 4/4] fix: fit the Settings window to the screen SettingsView fixed its frame at 520x1060, so on screens with less visible height (13" laptops, the CI runner) the window ran off the bottom and its last controls were unreachable. Give it a min/ideal/max height instead: ideal is the full 1060pt capped to the main screen's visible height, the scroll view handles overflow, and .windowResizability(.contentSize) lets the user resize between 480pt and 1060pt. Also makes the UI tests' scrollIntoView helper effective for the bottom controls. Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 3 +++ Codeagogo/CodeagogoApp.swift | 2 ++ Codeagogo/SettingsView.swift | 22 +++++++++++++++++++++- 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0ebc5d9..cb7853b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). ## [Unreleased] +### Fixed +- **Settings window on smaller screens**: The Settings window had a fixed 1060pt height, so on shorter displays (such as a 13" MacBook Air) its bottom extended off-screen and the logging and diagnostics controls could not be reached. It now opens at most as tall as the screen's visible area, scrolls when the content doesn't fit, and can be resized between 480pt and its full height. + ## [1.1.1] - 2026-06 ### Fixed diff --git a/Codeagogo/CodeagogoApp.swift b/Codeagogo/CodeagogoApp.swift index 76ab05e..e3ce3f7 100644 --- a/Codeagogo/CodeagogoApp.swift +++ b/Codeagogo/CodeagogoApp.swift @@ -22,6 +22,8 @@ struct Snomed_LookupApp: App { Settings { SettingsView() } + // Size the window from SettingsView's min/ideal/max height so it fits the screen. + .windowResizability(.contentSize) .commands { // Add items in the Help menu (native place for diagnostics) CommandGroup(after: .help) { diff --git a/Codeagogo/SettingsView.swift b/Codeagogo/SettingsView.swift index 404c2ad..623f25b 100644 --- a/Codeagogo/SettingsView.swift +++ b/Codeagogo/SettingsView.swift @@ -414,7 +414,27 @@ struct SettingsView: View { } .padding(16) } - .frame(width: 520, height: 1060) + .frame(width: 520) + .frame(minHeight: Self.minimumHeight, idealHeight: Self.fittingHeight, maxHeight: Self.contentHeight) + } + + // MARK: - Window Sizing + + /// Height at which all settings are visible without scrolling. + private static let contentHeight: CGFloat = 1060 + + /// Smallest height the window can be resized to. + private static let minimumHeight: CGFloat = 480 + + /// Space reserved for the window's title bar when fitting it to the screen. + private static let titleBarAllowance: CGFloat = 40 + + /// Initial window height: the full content height, capped to the main screen's + /// visible area so the window never extends past the bottom of smaller screens. + /// The scroll view handles the overflow. + private static var fittingHeight: CGFloat { + guard let visibleHeight = NSScreen.main?.visibleFrame.height else { return contentHeight } + return max(minimumHeight, min(contentHeight, visibleHeight - titleBarAllowance)) } // MARK: - Code System Helpers