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: