From 92b79696a37859898728f5f287e1dca1abe98b64 Mon Sep 17 00:00:00 2001
From: Ethan Dickson <ethan@coder.com>
Date: Thu, 3 Apr 2025 21:06:15 +1100
Subject: [PATCH 1/4] fix: improve file sync agent picker

---
 .../Views/FileSync/FileSyncConfig.swift         |  4 +---
 .../Views/FileSync/FileSyncSessionModal.swift   | 17 +++++++++--------
 .../Coder-Desktop/Views/VPN/VPNMenu.swift       |  9 +++++++++
 3 files changed, 19 insertions(+), 11 deletions(-)

diff --git a/Coder-Desktop/Coder-Desktop/Views/FileSync/FileSyncConfig.swift b/Coder-Desktop/Coder-Desktop/Views/FileSync/FileSyncConfig.swift
index 345928b..a3fac2b 100644
--- a/Coder-Desktop/Coder-Desktop/Views/FileSync/FileSyncConfig.swift
+++ b/Coder-Desktop/Coder-Desktop/Views/FileSync/FileSyncConfig.swift
@@ -107,9 +107,7 @@ struct FileSyncConfig<VPN: VPNService, FS: FileSyncDaemon>: View {
                 // When the Window is visible, poll for session updates every
                 // two seconds.
                 while !Task.isCancelled {
-                    if !fileSync.state.isFailed {
-                        await fileSync.refreshSessions()
-                    }
+                    await fileSync.refreshSessions()
                     try? await Task.sleep(for: .seconds(2))
                 }
             }.onAppear {
diff --git a/Coder-Desktop/Coder-Desktop/Views/FileSync/FileSyncSessionModal.swift b/Coder-Desktop/Coder-Desktop/Views/FileSync/FileSyncSessionModal.swift
index d398172..065eeb5 100644
--- a/Coder-Desktop/Coder-Desktop/Views/FileSync/FileSyncSessionModal.swift
+++ b/Coder-Desktop/Coder-Desktop/Views/FileSync/FileSyncSessionModal.swift
@@ -8,7 +8,7 @@ struct FileSyncSessionModal<VPN: VPNService, FS: FileSyncDaemon>: View {
     @EnvironmentObject private var fileSync: FS
 
     @State private var localPath: String = ""
-    @State private var workspace: Agent?
+    @State private var chosenAgent: String?
     @State private var remotePath: String = ""
 
     @State private var loading: Bool = false
@@ -37,12 +37,12 @@ struct FileSyncSessionModal<VPN: VPNService, FS: FileSyncDaemon>: View {
                     }
                 }
                 Section {
-                    Picker("Workspace", selection: $workspace) {
+                    Picker("Workspace", selection: $chosenAgent) {
                         ForEach(agents, id: \.id) { agent in
-                            Text(agent.primaryHost!).tag(agent)
+                            Text(agent.primaryHost!).tag(agent.primaryHost!)
                         }
                         // HACK: Silence error logs for no-selection.
-                        Divider().tag(nil as Agent?)
+                        Divider().tag(nil as String?)
                     }
                 }
                 Section {
@@ -55,15 +55,16 @@ struct FileSyncSessionModal<VPN: VPNService, FS: FileSyncDaemon>: View {
                 Button("Cancel", action: { dismiss() }).keyboardShortcut(.cancelAction)
                 Button(existingSession == nil ? "Add" : "Save") { Task { await submit() }}
                     .keyboardShortcut(.defaultAction)
+                    .disabled(localPath.isEmpty || remotePath.isEmpty || chosenAgent == nil)
             }.padding(20)
         }.onAppear {
             if let existingSession {
                 localPath = existingSession.alphaPath
-                workspace = agents.first { $0.primaryHost == existingSession.agentHost }
+                chosenAgent = agents.first { $0.primaryHost == existingSession.agentHost }?.primaryHost
                 remotePath = existingSession.betaPath
             } else {
                 // Set the picker to the first agent by default
-                workspace = agents.first
+                chosenAgent = agents.first?.primaryHost
             }
         }.disabled(loading)
             .alert("Error", isPresented: Binding(
@@ -76,7 +77,7 @@ struct FileSyncSessionModal<VPN: VPNService, FS: FileSyncDaemon>: View {
 
     func submit() async {
         createError = nil
-        guard let workspace else {
+        guard let chosenAgent else {
             return
         }
         loading = true
@@ -87,7 +88,7 @@ struct FileSyncSessionModal<VPN: VPNService, FS: FileSyncDaemon>: View {
             }
             try await fileSync.createSession(
                 localPath: localPath,
-                agentHost: workspace.primaryHost!,
+                agentHost: chosenAgent,
                 remotePath: remotePath
             )
         } catch {
diff --git a/Coder-Desktop/Coder-Desktop/Views/VPN/VPNMenu.swift b/Coder-Desktop/Coder-Desktop/Views/VPN/VPNMenu.swift
index 207f0d9..c1da06d 100644
--- a/Coder-Desktop/Coder-Desktop/Views/VPN/VPNMenu.swift
+++ b/Coder-Desktop/Coder-Desktop/Views/VPN/VPNMenu.swift
@@ -116,6 +116,15 @@ struct VPNMenu<VPN: VPNService, FS: FileSyncDaemon>: View {
             .environmentObject(vpn)
             .environmentObject(state)
             .onReceive(inspection.notice) { inspection.visit(self, $0) } // ViewInspector
+            .task {
+                // If there's a file sync session error, an icon will be displayed
+                // next to the file sync button. The file sync window polls more
+                // frequently when it's open.
+                while !Task.isCancelled {
+                    await fileSync.refreshSessions()
+                    try? await Task.sleep(for: .seconds(15))
+                }
+            }
     }
 
     private var vpnDisabled: Bool {

From 0997c9d63195739520abc777f19c9133818c2b62 Mon Sep 17 00:00:00 2001
From: Ethan Dickson <ethan@coder.com>
Date: Fri, 4 Apr 2025 13:43:54 +1100
Subject: [PATCH 2/4] review

---
 .../Coder-Desktop/Views/FileSync/FileSyncConfig.swift      | 7 -------
 Coder-Desktop/Coder-Desktop/Views/VPN/VPNMenu.swift        | 5 +----
 2 files changed, 1 insertion(+), 11 deletions(-)

diff --git a/Coder-Desktop/Coder-Desktop/Views/FileSync/FileSyncConfig.swift b/Coder-Desktop/Coder-Desktop/Views/FileSync/FileSyncConfig.swift
index a3fac2b..dc946c8 100644
--- a/Coder-Desktop/Coder-Desktop/Views/FileSync/FileSyncConfig.swift
+++ b/Coder-Desktop/Coder-Desktop/Views/FileSync/FileSyncConfig.swift
@@ -103,13 +103,6 @@ struct FileSyncConfig<VPN: VPNService, FS: FileSyncDaemon>: View {
                     // Opens the log file in Console
                     NSWorkspace.shared.open(fileSync.logFile)
                 }
-            }.task {
-                // When the Window is visible, poll for session updates every
-                // two seconds.
-                while !Task.isCancelled {
-                    await fileSync.refreshSessions()
-                    try? await Task.sleep(for: .seconds(2))
-                }
             }.onAppear {
                 isVisible = true
             }.onDisappear {
diff --git a/Coder-Desktop/Coder-Desktop/Views/VPN/VPNMenu.swift b/Coder-Desktop/Coder-Desktop/Views/VPN/VPNMenu.swift
index c1da06d..83757ef 100644
--- a/Coder-Desktop/Coder-Desktop/Views/VPN/VPNMenu.swift
+++ b/Coder-Desktop/Coder-Desktop/Views/VPN/VPNMenu.swift
@@ -117,12 +117,9 @@ struct VPNMenu<VPN: VPNService, FS: FileSyncDaemon>: View {
             .environmentObject(state)
             .onReceive(inspection.notice) { inspection.visit(self, $0) } // ViewInspector
             .task {
-                // If there's a file sync session error, an icon will be displayed
-                // next to the file sync button. The file sync window polls more
-                // frequently when it's open.
                 while !Task.isCancelled {
                     await fileSync.refreshSessions()
-                    try? await Task.sleep(for: .seconds(15))
+                    try? await Task.sleep(for: .seconds(2))
                 }
             }
     }

From 3c12680d9b73392f38a037bc995ce239e533da5f Mon Sep 17 00:00:00 2001
From: Ethan Dickson <ethan@coder.com>
Date: Mon, 7 Apr 2025 12:48:01 +1000
Subject: [PATCH 3/4] review

---
 .../Views/FileSync/FileSyncSessionModal.swift      | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/Coder-Desktop/Coder-Desktop/Views/FileSync/FileSyncSessionModal.swift b/Coder-Desktop/Coder-Desktop/Views/FileSync/FileSyncSessionModal.swift
index 065eeb5..160c94b 100644
--- a/Coder-Desktop/Coder-Desktop/Views/FileSync/FileSyncSessionModal.swift
+++ b/Coder-Desktop/Coder-Desktop/Views/FileSync/FileSyncSessionModal.swift
@@ -8,7 +8,7 @@ struct FileSyncSessionModal<VPN: VPNService, FS: FileSyncDaemon>: View {
     @EnvironmentObject private var fileSync: FS
 
     @State private var localPath: String = ""
-    @State private var chosenAgent: String?
+    @State private var remoteHostName: String?
     @State private var remotePath: String = ""
 
     @State private var loading: Bool = false
@@ -37,7 +37,7 @@ struct FileSyncSessionModal<VPN: VPNService, FS: FileSyncDaemon>: View {
                     }
                 }
                 Section {
-                    Picker("Workspace", selection: $chosenAgent) {
+                    Picker("Workspace", selection: $remoteHostName) {
                         ForEach(agents, id: \.id) { agent in
                             Text(agent.primaryHost!).tag(agent.primaryHost!)
                         }
@@ -55,16 +55,16 @@ struct FileSyncSessionModal<VPN: VPNService, FS: FileSyncDaemon>: View {
                 Button("Cancel", action: { dismiss() }).keyboardShortcut(.cancelAction)
                 Button(existingSession == nil ? "Add" : "Save") { Task { await submit() }}
                     .keyboardShortcut(.defaultAction)
-                    .disabled(localPath.isEmpty || remotePath.isEmpty || chosenAgent == nil)
+                    .disabled(localPath.isEmpty || remotePath.isEmpty || remoteHostName == nil)
             }.padding(20)
         }.onAppear {
             if let existingSession {
                 localPath = existingSession.alphaPath
-                chosenAgent = agents.first { $0.primaryHost == existingSession.agentHost }?.primaryHost
+                remoteHostName = agents.first { $0.primaryHost == existingSession.agentHost }?.primaryHost
                 remotePath = existingSession.betaPath
             } else {
                 // Set the picker to the first agent by default
-                chosenAgent = agents.first?.primaryHost
+                remoteHostName = agents.first?.primaryHost
             }
         }.disabled(loading)
             .alert("Error", isPresented: Binding(
@@ -77,7 +77,7 @@ struct FileSyncSessionModal<VPN: VPNService, FS: FileSyncDaemon>: View {
 
     func submit() async {
         createError = nil
-        guard let chosenAgent else {
+        guard let remoteHostName else {
             return
         }
         loading = true
@@ -88,7 +88,7 @@ struct FileSyncSessionModal<VPN: VPNService, FS: FileSyncDaemon>: View {
             }
             try await fileSync.createSession(
                 localPath: localPath,
-                agentHost: chosenAgent,
+                agentHost: remoteHostName,
                 remotePath: remotePath
             )
         } catch {

From a3518f2d48d6bb620dc122d08877be258ecbf6ae Mon Sep 17 00:00:00 2001
From: Ethan Dickson <ethan@coder.com>
Date: Mon, 7 Apr 2025 16:47:07 +1000
Subject: [PATCH 4/4] review

---
 .../Views/FileSync/FileSyncSessionModal.swift      | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/Coder-Desktop/Coder-Desktop/Views/FileSync/FileSyncSessionModal.swift b/Coder-Desktop/Coder-Desktop/Views/FileSync/FileSyncSessionModal.swift
index 160c94b..0e42ea0 100644
--- a/Coder-Desktop/Coder-Desktop/Views/FileSync/FileSyncSessionModal.swift
+++ b/Coder-Desktop/Coder-Desktop/Views/FileSync/FileSyncSessionModal.swift
@@ -8,7 +8,7 @@ struct FileSyncSessionModal<VPN: VPNService, FS: FileSyncDaemon>: View {
     @EnvironmentObject private var fileSync: FS
 
     @State private var localPath: String = ""
-    @State private var remoteHostName: String?
+    @State private var remoteHostname: String?
     @State private var remotePath: String = ""
 
     @State private var loading: Bool = false
@@ -37,7 +37,7 @@ struct FileSyncSessionModal<VPN: VPNService, FS: FileSyncDaemon>: View {
                     }
                 }
                 Section {
-                    Picker("Workspace", selection: $remoteHostName) {
+                    Picker("Workspace", selection: $remoteHostname) {
                         ForEach(agents, id: \.id) { agent in
                             Text(agent.primaryHost!).tag(agent.primaryHost!)
                         }
@@ -55,16 +55,16 @@ struct FileSyncSessionModal<VPN: VPNService, FS: FileSyncDaemon>: View {
                 Button("Cancel", action: { dismiss() }).keyboardShortcut(.cancelAction)
                 Button(existingSession == nil ? "Add" : "Save") { Task { await submit() }}
                     .keyboardShortcut(.defaultAction)
-                    .disabled(localPath.isEmpty || remotePath.isEmpty || remoteHostName == nil)
+                    .disabled(localPath.isEmpty || remotePath.isEmpty || remoteHostname == nil)
             }.padding(20)
         }.onAppear {
             if let existingSession {
                 localPath = existingSession.alphaPath
-                remoteHostName = agents.first { $0.primaryHost == existingSession.agentHost }?.primaryHost
+                remoteHostname = agents.first { $0.primaryHost == existingSession.agentHost }?.primaryHost
                 remotePath = existingSession.betaPath
             } else {
                 // Set the picker to the first agent by default
-                remoteHostName = agents.first?.primaryHost
+                remoteHostname = agents.first?.primaryHost
             }
         }.disabled(loading)
             .alert("Error", isPresented: Binding(
@@ -77,7 +77,7 @@ struct FileSyncSessionModal<VPN: VPNService, FS: FileSyncDaemon>: View {
 
     func submit() async {
         createError = nil
-        guard let remoteHostName else {
+        guard let remoteHostname else {
             return
         }
         loading = true
@@ -88,7 +88,7 @@ struct FileSyncSessionModal<VPN: VPNService, FS: FileSyncDaemon>: View {
             }
             try await fileSync.createSession(
                 localPath: localPath,
-                agentHost: remoteHostName,
+                agentHost: remoteHostname,
                 remotePath: remotePath
             )
         } catch {