From 72e0b05cf26579026fdebd9daf00446cdf566609 Mon Sep 17 00:00:00 2001 From: Ethan Dickson Date: Wed, 23 Apr 2025 13:54:01 +1000 Subject: [PATCH 1/5] feat: add progress messages when creating sync sessions --- .../Preview Content/PreviewFileSync.swift | 1 + .../Preview Content/PreviewVPN.swift | 20 +++++++++---------- .../Coder-Desktop/VPN/MenuState.swift | 17 ++++++++-------- .../Views/FileSync/FileSyncSessionModal.swift | 5 ++++- .../Coder-Desktop/Views/VPN/VPNMenuItem.swift | 9 ++++----- .../VPNLib/FileSync/FileSyncDaemon.swift | 3 +++ .../VPNLib/FileSync/FileSyncPrompting.swift | 3 +++ 7 files changed, 34 insertions(+), 24 deletions(-) diff --git a/Coder-Desktop/Coder-Desktop/Preview Content/PreviewFileSync.swift b/Coder-Desktop/Coder-Desktop/Preview Content/PreviewFileSync.swift index 1253e427..8da6b5fa 100644 --- a/Coder-Desktop/Coder-Desktop/Preview Content/PreviewFileSync.swift +++ b/Coder-Desktop/Coder-Desktop/Preview Content/PreviewFileSync.swift @@ -3,6 +3,7 @@ import VPNLib @MainActor final class PreviewFileSync: FileSyncDaemon { var logFile: URL = .init(filePath: "~/log.txt")! + var lastPromptMessage: String? var sessionState: [VPNLib.FileSyncSession] = [] diff --git a/Coder-Desktop/Coder-Desktop/Preview Content/PreviewVPN.swift b/Coder-Desktop/Coder-Desktop/Preview Content/PreviewVPN.swift index a3ef51e5..2c6e8d02 100644 --- a/Coder-Desktop/Coder-Desktop/Preview Content/PreviewVPN.swift +++ b/Coder-Desktop/Coder-Desktop/Preview Content/PreviewVPN.swift @@ -6,25 +6,25 @@ final class PreviewVPN: Coder_Desktop.VPNService { @Published var state: Coder_Desktop.VPNServiceState = .connected @Published var menuState: VPNMenuState = .init(agents: [ UUID(): Agent(id: UUID(), name: "dev", status: .error, hosts: ["asdf.coder"], wsName: "dogfood2", - wsID: UUID()), + wsID: UUID(), primaryHost: "asdf.coder"), UUID(): Agent(id: UUID(), name: "dev", status: .okay, hosts: ["asdf.coder"], - wsName: "testing-a-very-long-name", wsID: UUID()), + wsName: "testing-a-very-long-name", wsID: UUID(), primaryHost: "asdf.coder"), UUID(): Agent(id: UUID(), name: "dev", status: .warn, hosts: ["asdf.coder"], wsName: "opensrc", - wsID: UUID()), + wsID: UUID(), primaryHost: "asdf.coder"), UUID(): Agent(id: UUID(), name: "dev", status: .off, hosts: ["asdf.coder"], wsName: "gvisor", - wsID: UUID()), + wsID: UUID(), primaryHost: "asdf.coder"), UUID(): Agent(id: UUID(), name: "dev", status: .off, hosts: ["asdf.coder"], wsName: "example", - wsID: UUID()), + wsID: UUID(), primaryHost: "asdf.coder"), UUID(): Agent(id: UUID(), name: "dev", status: .error, hosts: ["asdf.coder"], wsName: "dogfood2", - wsID: UUID()), + wsID: UUID(), primaryHost: "asdf.coder"), UUID(): Agent(id: UUID(), name: "dev", status: .okay, hosts: ["asdf.coder"], - wsName: "testing-a-very-long-name", wsID: UUID()), + wsName: "testing-a-very-long-name", wsID: UUID(), primaryHost: "asdf.coder"), UUID(): Agent(id: UUID(), name: "dev", status: .warn, hosts: ["asdf.coder"], wsName: "opensrc", - wsID: UUID()), + wsID: UUID(), primaryHost: "asdf.coder"), UUID(): Agent(id: UUID(), name: "dev", status: .off, hosts: ["asdf.coder"], wsName: "gvisor", - wsID: UUID()), + wsID: UUID(), primaryHost: "asdf.coder"), UUID(): Agent(id: UUID(), name: "dev", status: .off, hosts: ["asdf.coder"], wsName: "example", - wsID: UUID()), + wsID: UUID(), primaryHost: "asdf.coder"), ], workspaces: [:]) let shouldFail: Bool let longError = "This is a long error to test the UI with long error messages" diff --git a/Coder-Desktop/Coder-Desktop/VPN/MenuState.swift b/Coder-Desktop/Coder-Desktop/VPN/MenuState.swift index 9c15aca3..c7f16749 100644 --- a/Coder-Desktop/Coder-Desktop/VPN/MenuState.swift +++ b/Coder-Desktop/Coder-Desktop/VPN/MenuState.swift @@ -18,8 +18,7 @@ struct Agent: Identifiable, Equatable, Comparable, Hashable { return lhs.wsName.localizedCompare(rhs.wsName) == .orderedAscending } - // Hosts arrive sorted by length, the shortest looks best in the UI. - var primaryHost: String? { hosts.first } + let primaryHost: String } enum AgentStatus: Int, Equatable, Comparable { @@ -69,6 +68,9 @@ struct VPNMenuState { invalidAgents.append(agent) return } + // Remove trailing dot if present + let nonEmptyHosts = agent.fqdn.map { $0.hasSuffix(".") ? String($0.dropLast()) : $0 } + // An existing agent with the same name, belonging to the same workspace // is from a previous workspace build, and should be removed. agents.filter { $0.value.name == agent.name && $0.value.wsID == wsID } @@ -81,10 +83,11 @@ struct VPNMenuState { name: agent.name, // If last handshake was not within last five minutes, the agent is unhealthy status: agent.lastHandshake.date > Date.now.addingTimeInterval(-300) ? .okay : .warn, - // Remove trailing dot if present - hosts: agent.fqdn.map { $0.hasSuffix(".") ? String($0.dropLast()) : $0 }, + hosts: nonEmptyHosts, wsName: workspace.name, - wsID: wsID + wsID: wsID, + // Hosts arrive sorted by length, the shortest looks best in the UI. + primaryHost: nonEmptyHosts.first!, ) } @@ -135,9 +138,7 @@ struct VPNMenuState { return items.sorted() } - var onlineAgents: [Agent] { - agents.map(\.value).filter { $0.primaryHost != nil } - } + var onlineAgents: [Agent] { agents.map(\.value) } mutating func clear() { agents.removeAll() diff --git a/Coder-Desktop/Coder-Desktop/Views/FileSync/FileSyncSessionModal.swift b/Coder-Desktop/Coder-Desktop/Views/FileSync/FileSyncSessionModal.swift index 66b20baf..86294ffb 100644 --- a/Coder-Desktop/Coder-Desktop/Views/FileSync/FileSyncSessionModal.swift +++ b/Coder-Desktop/Coder-Desktop/Views/FileSync/FileSyncSessionModal.swift @@ -40,7 +40,7 @@ struct FileSyncSessionModal: View { Section { Picker("Workspace", selection: $remoteHostname) { ForEach(agents, id: \.id) { agent in - Text(agent.primaryHost!).tag(agent.primaryHost!) + Text(agent.primaryHost).tag(agent.primaryHost) } // HACK: Silence error logs for no-selection. Divider().tag(nil as String?) @@ -62,6 +62,9 @@ struct FileSyncSessionModal: View { Divider() HStack { Spacer() + if let lastMessage = fileSync.lastPromptMessage { + Text(lastMessage).foregroundStyle(.secondary) + } Button("Cancel", action: { dismiss() }).keyboardShortcut(.cancelAction) Button(existingSession == nil ? "Add" : "Save") { Task { await submit() }} .keyboardShortcut(.defaultAction) diff --git a/Coder-Desktop/Coder-Desktop/Views/VPN/VPNMenuItem.swift b/Coder-Desktop/Coder-Desktop/Views/VPN/VPNMenuItem.swift index 700cefa3..1bc0b98b 100644 --- a/Coder-Desktop/Coder-Desktop/Views/VPN/VPNMenuItem.swift +++ b/Coder-Desktop/Coder-Desktop/Views/VPN/VPNMenuItem.swift @@ -66,7 +66,7 @@ struct MenuItemView: View { private var itemName: AttributedString { let name = switch item { - case let .agent(agent): agent.primaryHost ?? "\(item.wsName).\(state.hostnameSuffix)" + case let .agent(agent): agent.primaryHost case .offlineWorkspace: "\(item.wsName).\(state.hostnameSuffix)" } @@ -103,10 +103,10 @@ struct MenuItemView: View { } Spacer() }.buttonStyle(.plain) - if case let .agent(agent) = item, let copyableDNS = agent.primaryHost { + if case let .agent(agent) = item { Button { NSPasteboard.general.clearContents() - NSPasteboard.general.setString(copyableDNS, forType: .string) + NSPasteboard.general.setString(agent.primaryHost, forType: .string) } label: { Image(systemName: "doc.on.doc") .symbolVariant(.fill) @@ -143,7 +143,6 @@ struct MenuItemView: View { // If this menu item is an agent, and the user is logged in if case let .agent(agent) = item, let client = state.client, - let host = agent.primaryHost, let baseAccessURL = state.baseAccessURL, // Like the CLI, we'll re-use the existing session token to populate the URL let sessionToken = state.sessionToken @@ -166,7 +165,7 @@ struct MenuItemView: View { .flatMap(\.self) .first(where: { $0.id == agent.id }) { - apps = agentToApps(logger, wsAgent, host, baseAccessURL, sessionToken) + apps = agentToApps(logger, wsAgent, agent.primaryHost, baseAccessURL, sessionToken) } else { logger.error("Could not find agent '\(agent.id)' in workspace '\(item.wsName)' resources") } diff --git a/Coder-Desktop/VPNLib/FileSync/FileSyncDaemon.swift b/Coder-Desktop/VPNLib/FileSync/FileSyncDaemon.swift index 7f300fbe..1b842e1e 100644 --- a/Coder-Desktop/VPNLib/FileSync/FileSyncDaemon.swift +++ b/Coder-Desktop/VPNLib/FileSync/FileSyncDaemon.swift @@ -11,6 +11,7 @@ public protocol FileSyncDaemon: ObservableObject { var state: DaemonState { get } var sessionState: [FileSyncSession] { get } var logFile: URL { get } + var lastPromptMessage: String? { get } func tryStart() async func stop() async func refreshSessions() async @@ -47,6 +48,8 @@ public class MutagenDaemon: FileSyncDaemon { public let logFile: URL + @Published public var lastPromptMessage: String? + // Managing sync sessions could take a while, especially with prompting let sessionMgmtReqTimeout: TimeAmount = .seconds(15) diff --git a/Coder-Desktop/VPNLib/FileSync/FileSyncPrompting.swift b/Coder-Desktop/VPNLib/FileSync/FileSyncPrompting.swift index d5a49b42..dbd5cea8 100644 --- a/Coder-Desktop/VPNLib/FileSync/FileSyncPrompting.swift +++ b/Coder-Desktop/VPNLib/FileSync/FileSyncPrompting.swift @@ -28,6 +28,7 @@ extension MutagenDaemon { try initResp.ensureValid(first: true, allowPrompts: allowPrompts) Task.detached(priority: .background) { + defer { Task { @MainActor in self.lastPromptMessage = nil } } do { while let msg = try await iter.next() { try msg.ensureValid(first: false, allowPrompts: allowPrompts) @@ -39,6 +40,8 @@ extension MutagenDaemon { } // Any other messages that require a non-empty response will // cause the create op to fail, showing an error. This is ok for now. + } else { + Task { @MainActor in self.lastPromptMessage = msg.message } } try await stream.requestStream.send(reply) } From 5963d9d643bb7b74cb374711fd3ddb2950ebfd67 Mon Sep 17 00:00:00 2001 From: Ethan Dickson Date: Wed, 23 Apr 2025 14:05:32 +1000 Subject: [PATCH 2/5] fixup tests --- .../Preview Content/PreviewFileSync.swift | 8 ++++++-- Coder-Desktop/Coder-Desktop/VPN/MenuState.swift | 2 +- .../Views/FileSync/FileSyncSessionModal.swift | 13 ++++++++++--- Coder-Desktop/Coder-DesktopTests/AgentsTests.swift | 3 ++- Coder-Desktop/Coder-DesktopTests/Util.swift | 7 ++++++- Coder-Desktop/VPNLib/FileSync/FileSyncDaemon.swift | 8 ++++---- .../VPNLib/FileSync/FileSyncManagement.swift | 7 +++++-- .../VPNLib/FileSync/FileSyncPrompting.swift | 8 +++++--- 8 files changed, 39 insertions(+), 17 deletions(-) diff --git a/Coder-Desktop/Coder-Desktop/Preview Content/PreviewFileSync.swift b/Coder-Desktop/Coder-Desktop/Preview Content/PreviewFileSync.swift index 8da6b5fa..fa644751 100644 --- a/Coder-Desktop/Coder-Desktop/Preview Content/PreviewFileSync.swift +++ b/Coder-Desktop/Coder-Desktop/Preview Content/PreviewFileSync.swift @@ -3,7 +3,6 @@ import VPNLib @MainActor final class PreviewFileSync: FileSyncDaemon { var logFile: URL = .init(filePath: "~/log.txt")! - var lastPromptMessage: String? var sessionState: [VPNLib.FileSyncSession] = [] @@ -21,7 +20,12 @@ final class PreviewFileSync: FileSyncDaemon { state = .stopped } - func createSession(arg _: CreateSyncSessionRequest) async throws(DaemonError) {} + func createSession( + arg _: CreateSyncSessionRequest, + promptCallback _: ( + @MainActor (String) -> Void + )? + ) async throws(DaemonError) {} func deleteSessions(ids _: [String]) async throws(VPNLib.DaemonError) {} diff --git a/Coder-Desktop/Coder-Desktop/VPN/MenuState.swift b/Coder-Desktop/Coder-Desktop/VPN/MenuState.swift index c7f16749..59dfae08 100644 --- a/Coder-Desktop/Coder-Desktop/VPN/MenuState.swift +++ b/Coder-Desktop/Coder-Desktop/VPN/MenuState.swift @@ -87,7 +87,7 @@ struct VPNMenuState { wsName: workspace.name, wsID: wsID, // Hosts arrive sorted by length, the shortest looks best in the UI. - primaryHost: nonEmptyHosts.first!, + primaryHost: nonEmptyHosts.first! ) } diff --git a/Coder-Desktop/Coder-Desktop/Views/FileSync/FileSyncSessionModal.swift b/Coder-Desktop/Coder-Desktop/Views/FileSync/FileSyncSessionModal.swift index 86294ffb..ca579e14 100644 --- a/Coder-Desktop/Coder-Desktop/Views/FileSync/FileSyncSessionModal.swift +++ b/Coder-Desktop/Coder-Desktop/Views/FileSync/FileSyncSessionModal.swift @@ -15,6 +15,8 @@ struct FileSyncSessionModal: View { @State private var createError: DaemonError? @State private var pickingRemote: Bool = false + @State private var lastPromptMessage: String? + var body: some View { let agents = vpn.menuState.onlineAgents VStack(spacing: 0) { @@ -62,8 +64,11 @@ struct FileSyncSessionModal: View { Divider() HStack { Spacer() - if let lastMessage = fileSync.lastPromptMessage { - Text(lastMessage).foregroundStyle(.secondary) + if let msg = lastPromptMessage { + Text(msg).foregroundStyle(.secondary) + } + if loading { + ProgressView().controlSize(.small) } Button("Cancel", action: { dismiss() }).keyboardShortcut(.cancelAction) Button(existingSession == nil ? "Add" : "Save") { Task { await submit() }} @@ -106,8 +111,10 @@ struct FileSyncSessionModal: View { arg: .init( alpha: .init(path: localPath, protocolKind: .local), beta: .init(path: remotePath, protocolKind: .ssh(host: remoteHostname)) - ) + ), + promptCallback: { lastPromptMessage = $0 }, ) + lastPromptMessage = nil } catch { createError = error return diff --git a/Coder-Desktop/Coder-DesktopTests/AgentsTests.swift b/Coder-Desktop/Coder-DesktopTests/AgentsTests.swift index ac98bd3c..fc53e046 100644 --- a/Coder-Desktop/Coder-DesktopTests/AgentsTests.swift +++ b/Coder-Desktop/Coder-DesktopTests/AgentsTests.swift @@ -27,7 +27,8 @@ struct AgentsTests { status: status, hosts: ["a\($0).coder"], wsName: "ws\($0)", - wsID: UUID() + wsID: UUID(), + primaryHost: "a\($0).coder", ) return (agent.id, agent) }) diff --git a/Coder-Desktop/Coder-DesktopTests/Util.swift b/Coder-Desktop/Coder-DesktopTests/Util.swift index c5239a92..6c7bc206 100644 --- a/Coder-Desktop/Coder-DesktopTests/Util.swift +++ b/Coder-Desktop/Coder-DesktopTests/Util.swift @@ -31,6 +31,8 @@ class MockVPNService: VPNService, ObservableObject { class MockFileSyncDaemon: FileSyncDaemon { var logFile: URL = .init(filePath: "~/log.txt") + var lastPromptMessage: String? + var sessionState: [VPNLib.FileSyncSession] = [] func refreshSessions() async {} @@ -47,7 +49,10 @@ class MockFileSyncDaemon: FileSyncDaemon { [] } - func createSession(arg _: CreateSyncSessionRequest) async throws(DaemonError) {} + func createSession( + arg _: CreateSyncSessionRequest, + promptCallback _: (@MainActor (String) -> Void)? + ) async throws(DaemonError) {} func pauseSessions(ids _: [String]) async throws(VPNLib.DaemonError) {} diff --git a/Coder-Desktop/VPNLib/FileSync/FileSyncDaemon.swift b/Coder-Desktop/VPNLib/FileSync/FileSyncDaemon.swift index 1b842e1e..f8f1dc71 100644 --- a/Coder-Desktop/VPNLib/FileSync/FileSyncDaemon.swift +++ b/Coder-Desktop/VPNLib/FileSync/FileSyncDaemon.swift @@ -11,11 +11,13 @@ public protocol FileSyncDaemon: ObservableObject { var state: DaemonState { get } var sessionState: [FileSyncSession] { get } var logFile: URL { get } - var lastPromptMessage: String? { get } func tryStart() async func stop() async func refreshSessions() async - func createSession(arg: CreateSyncSessionRequest) async throws(DaemonError) + func createSession( + arg: CreateSyncSessionRequest, + promptCallback: (@MainActor (String) -> Void)? + ) async throws(DaemonError) func deleteSessions(ids: [String]) async throws(DaemonError) func pauseSessions(ids: [String]) async throws(DaemonError) func resumeSessions(ids: [String]) async throws(DaemonError) @@ -48,8 +50,6 @@ public class MutagenDaemon: FileSyncDaemon { public let logFile: URL - @Published public var lastPromptMessage: String? - // Managing sync sessions could take a while, especially with prompting let sessionMgmtReqTimeout: TimeAmount = .seconds(15) diff --git a/Coder-Desktop/VPNLib/FileSync/FileSyncManagement.swift b/Coder-Desktop/VPNLib/FileSync/FileSyncManagement.swift index aaf86b18..80fa76ff 100644 --- a/Coder-Desktop/VPNLib/FileSync/FileSyncManagement.swift +++ b/Coder-Desktop/VPNLib/FileSync/FileSyncManagement.swift @@ -17,7 +17,10 @@ public extension MutagenDaemon { sessionState = sessions.sessionStates.map { FileSyncSession(state: $0) } } - func createSession(arg: CreateSyncSessionRequest) async throws(DaemonError) { + func createSession( + arg: CreateSyncSessionRequest, + promptCallback: (@MainActor (String) -> Void)? = nil + ) async throws(DaemonError) { if case .stopped = state { do throws(DaemonError) { try await start() @@ -26,7 +29,7 @@ public extension MutagenDaemon { throw error } } - let (stream, promptID) = try await host() + let (stream, promptID) = try await host(promptCallback: promptCallback) defer { stream.cancel() } let req = Synchronization_CreateRequest.with { req in req.prompter = promptID diff --git a/Coder-Desktop/VPNLib/FileSync/FileSyncPrompting.swift b/Coder-Desktop/VPNLib/FileSync/FileSyncPrompting.swift index dbd5cea8..7b8307a2 100644 --- a/Coder-Desktop/VPNLib/FileSync/FileSyncPrompting.swift +++ b/Coder-Desktop/VPNLib/FileSync/FileSyncPrompting.swift @@ -3,7 +3,10 @@ import GRPC extension MutagenDaemon { typealias PromptStream = GRPCAsyncBidirectionalStreamingCall - func host(allowPrompts: Bool = true) async throws(DaemonError) -> (PromptStream, identifier: String) { + func host( + allowPrompts: Bool = true, + promptCallback: (@MainActor (String) -> Void)? = nil + ) async throws(DaemonError) -> (PromptStream, identifier: String) { let stream = client!.prompt.makeHostCall() do { @@ -28,7 +31,6 @@ extension MutagenDaemon { try initResp.ensureValid(first: true, allowPrompts: allowPrompts) Task.detached(priority: .background) { - defer { Task { @MainActor in self.lastPromptMessage = nil } } do { while let msg = try await iter.next() { try msg.ensureValid(first: false, allowPrompts: allowPrompts) @@ -41,7 +43,7 @@ extension MutagenDaemon { // Any other messages that require a non-empty response will // cause the create op to fail, showing an error. This is ok for now. } else { - Task { @MainActor in self.lastPromptMessage = msg.message } + Task { @MainActor in promptCallback?(msg.message) } } try await stream.requestStream.send(reply) } From c6201353a93e09db84795102ad5399281dc5a7af Mon Sep 17 00:00:00 2001 From: Ethan Dickson Date: Wed, 23 Apr 2025 17:44:40 +1000 Subject: [PATCH 3/5] tests --- .../Coder-DesktopTests/FileSyncDaemonTests.swift | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Coder-Desktop/Coder-DesktopTests/FileSyncDaemonTests.swift b/Coder-Desktop/Coder-DesktopTests/FileSyncDaemonTests.swift index 916faf64..85c0bcfa 100644 --- a/Coder-Desktop/Coder-DesktopTests/FileSyncDaemonTests.swift +++ b/Coder-Desktop/Coder-DesktopTests/FileSyncDaemonTests.swift @@ -61,6 +61,7 @@ class FileSyncDaemonTests { #expect(statesEqual(daemon.state, .stopped)) #expect(daemon.sessionState.count == 0) + var promptMessages: [String] = [] try await daemon.createSession( arg: .init( alpha: .init( @@ -71,9 +72,16 @@ class FileSyncDaemonTests { path: mutagenBetaDirectory.path(), protocolKind: .local ) - ) + ), + promptCallback: { + promptMessages.append($0) + } ) + // There should be at least one prompt message + // Usually "Creating session..." + #expect(promptMessages.count > 0) + // Daemon should have started itself #expect(statesEqual(daemon.state, .running)) #expect(daemon.sessionState.count == 1) From 4ca3b6f47c29c85a30958e1e4b24a0b5fdb4fe23 Mon Sep 17 00:00:00 2001 From: Ethan Dickson Date: Wed, 23 Apr 2025 17:55:02 +1000 Subject: [PATCH 4/5] owned swift 6.1 update again --- .../Coder-Desktop/Views/FileSync/FileSyncSessionModal.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Coder-Desktop/Coder-Desktop/Views/FileSync/FileSyncSessionModal.swift b/Coder-Desktop/Coder-Desktop/Views/FileSync/FileSyncSessionModal.swift index ca579e14..3e48ffd4 100644 --- a/Coder-Desktop/Coder-Desktop/Views/FileSync/FileSyncSessionModal.swift +++ b/Coder-Desktop/Coder-Desktop/Views/FileSync/FileSyncSessionModal.swift @@ -112,7 +112,7 @@ struct FileSyncSessionModal: View { alpha: .init(path: localPath, protocolKind: .local), beta: .init(path: remotePath, protocolKind: .ssh(host: remoteHostname)) ), - promptCallback: { lastPromptMessage = $0 }, + promptCallback: { lastPromptMessage = $0 } ) lastPromptMessage = nil } catch { From ea2c8a0e707d1dd44cd114e584b989a3d5b60a95 Mon Sep 17 00:00:00 2001 From: Ethan Dickson Date: Wed, 23 Apr 2025 18:22:57 +1000 Subject: [PATCH 5/5] owned by swift 6.1 update again --- Coder-Desktop/Coder-DesktopTests/AgentsTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Coder-Desktop/Coder-DesktopTests/AgentsTests.swift b/Coder-Desktop/Coder-DesktopTests/AgentsTests.swift index fc53e046..62c1607f 100644 --- a/Coder-Desktop/Coder-DesktopTests/AgentsTests.swift +++ b/Coder-Desktop/Coder-DesktopTests/AgentsTests.swift @@ -28,7 +28,7 @@ struct AgentsTests { hosts: ["a\($0).coder"], wsName: "ws\($0)", wsID: UUID(), - primaryHost: "a\($0).coder", + primaryHost: "a\($0).coder" ) return (agent.id, agent) }) pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy