diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c87a4306..293bfb9c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,7 +22,7 @@ concurrency: jobs: test: name: test - runs-on: ${{ github.repository_owner == 'coder' && 'depot-macos-latest' || 'macos-latest'}} + runs-on: ${{ github.repository_owner == 'coder' && 'depot-macos-26' || 'macos-26'}} steps: - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -34,7 +34,7 @@ jobs: - name: Switch XCode Version uses: maxim-lobanov/setup-xcode@ed7a3b1fda3918c0306d1b724322adc0b8cc0a90 # v1.7.0 with: - xcode-version: "16.4.0" + xcode-version: "26.5.0" - name: Setup Nix uses: ./.github/actions/nix-devshell @@ -43,7 +43,7 @@ jobs: format: name: fmt - runs-on: ${{ github.repository_owner == 'coder' && 'depot-macos-latest' || 'macos-latest'}} + runs-on: ${{ github.repository_owner == 'coder' && 'depot-macos-26' || 'macos-26'}} steps: - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 @@ -55,7 +55,7 @@ jobs: - name: Switch XCode Version uses: maxim-lobanov/setup-xcode@ed7a3b1fda3918c0306d1b724322adc0b8cc0a90 # v1.7.0 with: - xcode-version: "16.4.0" + xcode-version: "26.5.0" - name: Setup Nix uses: ./.github/actions/nix-devshell @@ -64,7 +64,7 @@ jobs: lint: name: lint - runs-on: ${{ github.repository_owner == 'coder' && 'depot-macos-latest' || 'macos-latest'}} + runs-on: ${{ github.repository_owner == 'coder' && 'depot-macos-26' || 'macos-26'}} steps: - name: Checkout uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b9eee49a..fc94ade5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -25,7 +25,7 @@ concurrency: jobs: build: name: Build Coder Desktop - runs-on: ${{ github.repository_owner == 'coder' && 'depot-macos-latest' || 'macos-latest'}} + runs-on: ${{ github.repository_owner == 'coder' && 'depot-macos-26' || 'macos-26'}} if: ${{ github.repository_owner == 'coder' }} permissions: # To upload assets to the release @@ -43,7 +43,7 @@ jobs: - name: Switch XCode Version uses: maxim-lobanov/setup-xcode@ed7a3b1fda3918c0306d1b724322adc0b8cc0a90 # v1.7.0 with: - xcode-version: "16.4.0" + xcode-version: "26.5.0" - name: Setup Nix uses: ./.github/actions/nix-devshell @@ -107,7 +107,7 @@ jobs: update-cask: name: Update homebrew-coder cask - runs-on: ${{ github.repository_owner == 'coder' && 'depot-macos-latest' || 'macos-latest'}} + runs-on: ${{ github.repository_owner == 'coder' && 'depot-macos-26' || 'macos-26'}} if: ${{ github.repository_owner == 'coder' && github.event_name == 'release' }} needs: build steps: diff --git a/Coder-Desktop/Coder-Desktop/AppIcon.icon/Assets/04_Coder_Shorthand_Logo_RGB_White.svg b/Coder-Desktop/Coder-Desktop/AppIcon.icon/Assets/04_Coder_Shorthand_Logo_RGB_White.svg new file mode 100644 index 00000000..f60ab682 --- /dev/null +++ b/Coder-Desktop/Coder-Desktop/AppIcon.icon/Assets/04_Coder_Shorthand_Logo_RGB_White.svg @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Coder-Desktop/Coder-Desktop/AppIcon.icon/icon.json b/Coder-Desktop/Coder-Desktop/AppIcon.icon/icon.json new file mode 100644 index 00000000..857e8cc5 --- /dev/null +++ b/Coder-Desktop/Coder-Desktop/AppIcon.icon/icon.json @@ -0,0 +1,36 @@ +{ + "fill" : { + "solid" : "display-p3:0.00000,0.00000,0.00000,1.00000" + }, + "groups" : [ + { + "layers" : [ + { + "glass" : true, + "image-name" : "04_Coder_Shorthand_Logo_RGB_White.svg", + "name" : "04_Coder_Shorthand_Logo_RGB_White", + "position" : { + "scale" : 1.5, + "translation-in-points" : [ + 0, + 0 + ] + } + } + ], + "shadow" : { + "kind" : "neutral", + "opacity" : 0.5 + }, + "translucency" : { + "enabled" : true, + "value" : 0.5 + } + } + ], + "supported-platforms" : { + "squares" : [ + "macOS" + ] + } +} \ No newline at end of file diff --git a/Coder-Desktop/Coder-Desktop/Assets.xcassets/AppIcon.appiconset/1024.png b/Coder-Desktop/Coder-Desktop/Assets.xcassets/AppIcon.appiconset/1024.png deleted file mode 100644 index 7ab987c4..00000000 Binary files a/Coder-Desktop/Coder-Desktop/Assets.xcassets/AppIcon.appiconset/1024.png and /dev/null differ diff --git a/Coder-Desktop/Coder-Desktop/Assets.xcassets/AppIcon.appiconset/128.png b/Coder-Desktop/Coder-Desktop/Assets.xcassets/AppIcon.appiconset/128.png deleted file mode 100644 index 82746ce3..00000000 Binary files a/Coder-Desktop/Coder-Desktop/Assets.xcassets/AppIcon.appiconset/128.png and /dev/null differ diff --git a/Coder-Desktop/Coder-Desktop/Assets.xcassets/AppIcon.appiconset/128@2x.png b/Coder-Desktop/Coder-Desktop/Assets.xcassets/AppIcon.appiconset/128@2x.png deleted file mode 100644 index bdb8b9ba..00000000 Binary files a/Coder-Desktop/Coder-Desktop/Assets.xcassets/AppIcon.appiconset/128@2x.png and /dev/null differ diff --git a/Coder-Desktop/Coder-Desktop/Assets.xcassets/AppIcon.appiconset/16.png b/Coder-Desktop/Coder-Desktop/Assets.xcassets/AppIcon.appiconset/16.png deleted file mode 100644 index 72cda2de..00000000 Binary files a/Coder-Desktop/Coder-Desktop/Assets.xcassets/AppIcon.appiconset/16.png and /dev/null differ diff --git a/Coder-Desktop/Coder-Desktop/Assets.xcassets/AppIcon.appiconset/16@2x.png b/Coder-Desktop/Coder-Desktop/Assets.xcassets/AppIcon.appiconset/16@2x.png deleted file mode 100644 index 52ebf9d0..00000000 Binary files a/Coder-Desktop/Coder-Desktop/Assets.xcassets/AppIcon.appiconset/16@2x.png and /dev/null differ diff --git a/Coder-Desktop/Coder-Desktop/Assets.xcassets/AppIcon.appiconset/256.png b/Coder-Desktop/Coder-Desktop/Assets.xcassets/AppIcon.appiconset/256.png deleted file mode 100644 index bdb8b9ba..00000000 Binary files a/Coder-Desktop/Coder-Desktop/Assets.xcassets/AppIcon.appiconset/256.png and /dev/null differ diff --git a/Coder-Desktop/Coder-Desktop/Assets.xcassets/AppIcon.appiconset/32.png b/Coder-Desktop/Coder-Desktop/Assets.xcassets/AppIcon.appiconset/32.png deleted file mode 100644 index 52ebf9d0..00000000 Binary files a/Coder-Desktop/Coder-Desktop/Assets.xcassets/AppIcon.appiconset/32.png and /dev/null differ diff --git a/Coder-Desktop/Coder-Desktop/Assets.xcassets/AppIcon.appiconset/32@2x.png b/Coder-Desktop/Coder-Desktop/Assets.xcassets/AppIcon.appiconset/32@2x.png deleted file mode 100644 index 1b4d34d8..00000000 Binary files a/Coder-Desktop/Coder-Desktop/Assets.xcassets/AppIcon.appiconset/32@2x.png and /dev/null differ diff --git a/Coder-Desktop/Coder-Desktop/Assets.xcassets/AppIcon.appiconset/512.png b/Coder-Desktop/Coder-Desktop/Assets.xcassets/AppIcon.appiconset/512.png deleted file mode 100644 index 5a3a95b2..00000000 Binary files a/Coder-Desktop/Coder-Desktop/Assets.xcassets/AppIcon.appiconset/512.png and /dev/null differ diff --git a/Coder-Desktop/Coder-Desktop/Assets.xcassets/AppIcon.appiconset/512@2x.png b/Coder-Desktop/Coder-Desktop/Assets.xcassets/AppIcon.appiconset/512@2x.png deleted file mode 100644 index 5a3a95b2..00000000 Binary files a/Coder-Desktop/Coder-Desktop/Assets.xcassets/AppIcon.appiconset/512@2x.png and /dev/null differ diff --git a/Coder-Desktop/Coder-Desktop/Assets.xcassets/AppIcon.appiconset/Contents.json b/Coder-Desktop/Coder-Desktop/Assets.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index 417149d7..00000000 --- a/Coder-Desktop/Coder-Desktop/Assets.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,68 +0,0 @@ -{ - "images": [ - { - "filename": "16.png", - "idiom": "mac", - "scale": "1x", - "size": "16x16" - }, - { - "filename": "16@2x.png", - "idiom": "mac", - "scale": "2x", - "size": "16x16" - }, - { - "filename": "32.png", - "idiom": "mac", - "scale": "1x", - "size": "32x32" - }, - { - "filename": "32@2x.png", - "idiom": "mac", - "scale": "2x", - "size": "32x32" - }, - { - "filename": "128.png", - "idiom": "mac", - "scale": "1x", - "size": "128x128" - }, - { - "filename": "128@2x.png", - "idiom": "mac", - "scale": "2x", - "size": "128x128" - }, - { - "filename": "256.png", - "idiom": "mac", - "scale": "1x", - "size": "256x256" - }, - { - "filename": "512.png", - "idiom": "mac", - "scale": "2x", - "size": "256x256" - }, - { - "filename": "512@2x.png", - "idiom": "mac", - "scale": "1x", - "size": "512x512" - }, - { - "filename": "1024.png", - "idiom": "mac", - "scale": "2x", - "size": "512x512" - } - ], - "info": { - "author": "xcode", - "version": 1 - } -} diff --git a/Coder-Desktop/Coder-Desktop/Views/LoginForm.swift b/Coder-Desktop/Coder-Desktop/Views/LoginForm.swift index 0ac4030c..4218634d 100644 --- a/Coder-Desktop/Coder-Desktop/Views/LoginForm.swift +++ b/Coder-Desktop/Coder-Desktop/Views/LoginForm.swift @@ -129,6 +129,7 @@ struct LoginForm: View { private var sessionTokenPage: some View { VStack(alignment: .leading, spacing: 0) { + Spacer() Form { Section { TextField( diff --git a/Coder-Desktop/VPN/PacketTunnelProvider.swift b/Coder-Desktop/VPN/PacketTunnelProvider.swift index 70b50e64..20bb02f1 100644 --- a/Coder-Desktop/VPN/PacketTunnelProvider.swift +++ b/Coder-Desktop/VPN/PacketTunnelProvider.swift @@ -43,7 +43,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider, @unchecked Sendable { return nil } - override func startTunnel( + override nonisolated(nonsending) func startTunnel( options _: [String: NSObject]? ) async throws { globalHelperXPCClient.ptp = self diff --git a/Coder-Desktop/VPNLib/TunnelDaemon.swift b/Coder-Desktop/VPNLib/TunnelDaemon.swift index 9797d0e4..f7a5118a 100644 --- a/Coder-Desktop/VPNLib/TunnelDaemon.swift +++ b/Coder-Desktop/VPNLib/TunnelDaemon.swift @@ -15,14 +15,17 @@ public actor TunnelDaemon { } private var monitorTask: Task? - private var onFail: (TunnelDaemonError) -> Void + private var onFail: @Sendable (TunnelDaemonError) -> Void public var writeHandle: FileHandle { tunnelReadPipe.fileHandleForWriting } public var readHandle: FileHandle { tunnelWritePipe.fileHandleForReading } var pid: pid_t? - public init(binaryPath: URL, onFail: @escaping (TunnelDaemonError) -> Void) async throws(TunnelDaemonError) { + public init( + binaryPath: URL, + onFail: @Sendable @escaping (TunnelDaemonError) -> Void + ) async throws(TunnelDaemonError) { self.onFail = onFail tunnelReadPipe = Pipe() tunnelWritePipe = Pipe() diff --git a/Coder-Desktop/VPNLibTests/TunnelDaemonTests.swift b/Coder-Desktop/VPNLibTests/TunnelDaemonTests.swift index ac1861e6..24d65aad 100644 --- a/Coder-Desktop/VPNLibTests/TunnelDaemonTests.swift +++ b/Coder-Desktop/VPNLibTests/TunnelDaemonTests.swift @@ -22,18 +22,21 @@ struct TunnelDaemonTests { let executableURL = try createTempExecutable(content: longRunningScript) defer { try? FileManager.default.removeItem(at: executableURL) } - var failureCalled = false - let daemon = try await TunnelDaemon(binaryPath: executableURL) { _ in - failureCalled = true + let (errors, continuation) = AsyncStream.makeStream(of: TunnelDaemonError.self) + let daemon = try await TunnelDaemon(binaryPath: executableURL) { error in + continuation.yield(error) } await #expect(daemon.state.isRunning) - #expect(!failureCalled) await #expect(daemon.readHandle.fileDescriptor >= 0) await #expect(daemon.writeHandle.fileDescriptor >= 0) try await daemon.close() await #expect(daemon.state.isStopped) + + continuation.finish() + let failure = await errors.first(where: { _ in true }) + #expect(failure == nil, "onFail should not have been called") } @Test func daemonHandlesFailure() async throws { @@ -45,14 +48,15 @@ struct TunnelDaemonTests { let executableURL = try createTempExecutable(content: immediateExitScript) defer { try? FileManager.default.removeItem(at: executableURL) } - var capturedError: TunnelDaemonError? + let (errors, continuation) = AsyncStream.makeStream(of: TunnelDaemonError.self) let daemon = try await TunnelDaemon(binaryPath: executableURL) { error in - capturedError = error + continuation.yield(error) } - #expect(await eventually(timeout: .milliseconds(500), interval: .milliseconds(10)) { @MainActor in - capturedError != nil - }) + guard let capturedError = await errors.first(where: { _ in true }) else { + Issue.record("onFail was never called") + return + } if case let .terminated(termination) = capturedError { if case let .exited(status) = termination { @@ -61,7 +65,7 @@ struct TunnelDaemonTests { Issue.record("Expected exited termination, got \(termination)") } } else { - Issue.record("Expected terminated error, got \(String(describing: capturedError))") + Issue.record("Expected terminated error, got \(capturedError)") } await #expect(daemon.state.isFailed) @@ -77,9 +81,9 @@ struct TunnelDaemonTests { let executableURL = try createTempExecutable(content: script) defer { try? FileManager.default.removeItem(at: executableURL) } - var capturedError: TunnelDaemonError? + let (errors, continuation) = AsyncStream.makeStream(of: TunnelDaemonError.self) let daemon = try await TunnelDaemon(binaryPath: executableURL) { error in - capturedError = error + continuation.yield(error) } await #expect(daemon.state.isRunning) @@ -91,9 +95,10 @@ struct TunnelDaemonTests { kill(pid, SIGKILL) - #expect(await eventually(timeout: .milliseconds(500), interval: .milliseconds(10)) { @MainActor in - capturedError != nil - }) + guard let capturedError = await errors.first(where: { _ in true }) else { + Issue.record("onFail was never called") + return + } if case let .terminated(termination) = capturedError { if case let .unhandledException(status) = termination { @@ -102,7 +107,7 @@ struct TunnelDaemonTests { Issue.record("Expected unhandledException termination, got \(termination)") } } else { - Issue.record("Expected terminated error, got \(String(describing: capturedError))") + Issue.record("Expected terminated error, got \(capturedError)") } } diff --git a/Coder-Desktop/project.yml b/Coder-Desktop/project.yml index fd648e4b..49690429 100644 --- a/Coder-Desktop/project.yml +++ b/Coder-Desktop/project.yml @@ -8,6 +8,9 @@ options: fileTypes: proto: buildPhase: none + "icon": + file: true + settings: base: