From 371bb822b06546f970c82ab28d37c787f8da4bd0 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Thu, 22 Oct 2026 00:35:59 +0000 Subject: [PATCH 1/3] fix(swift-ios): seed Add Project paths from the environment's base directory Carry settings.addProjectBaseDirectory from each environment's server config through FeatureEnvironment so the folder browser, existing-folder path, and clone destination start there, falling back to ~/ when unset or blank. Closes #16507 Co-Authored-By: Claude Opus 5.5 --- apps/swift-ios/App/NativeFeatureClient.swift | 1 + apps/swift-ios/Core/ServerConfigModels.swift | 3 +++ .../Features/Shared/FeatureModels.swift | 4 +++- .../Features/Workspace/AddProjectView.swift | 16 ++++++++++++---- 4 files changed, 19 insertions(+), 5 deletions(-) diff --git a/apps/swift-ios/App/NativeFeatureClient.swift b/apps/swift-ios/App/NativeFeatureClient.swift index 5793e527de364..10257bcc63a97 100644 --- a/apps/swift-ios/App/NativeFeatureClient.swift +++ b/apps/swift-ios/App/NativeFeatureClient.swift @@ -6710,6 +6710,7 @@ final class NativeFeatureClient: FeatureClient, FeatureDeviceManaging, mapped.machineIcon = serverConfigsByEnvironmentID[environment.id]?.settings?.environmentIcon ?? environment.descriptor?.platform.machine mapped.newProjectsRoot = serverConfigsByEnvironmentID[environment.id]?.newProjectsRoot + mapped.addProjectBaseDirectory = serverConfigsByEnvironmentID[environment.id]?.settings?.addProjectBaseDirectory mapped.supportsScratch = serverConfigsByEnvironmentID[environment.id]?.scratchWorkspaceRoot != nil mapped.canCustomizeIcon = serverConfigsByEnvironmentID[environment.id]?.environment?.capabilities.environmentIcon ?? environment.descriptor?.capabilities.environmentIcon diff --git a/apps/swift-ios/Core/ServerConfigModels.swift b/apps/swift-ios/Core/ServerConfigModels.swift index 6eb5f1af31ae9..ab284f3154648 100644 --- a/apps/swift-ios/Core/ServerConfigModels.swift +++ b/apps/swift-ios/Core/ServerConfigModels.swift @@ -221,6 +221,7 @@ public struct ServerSettingsSnapshot: Codable, Equatable, Sendable { public var environmentIcon: String? = nil public var sourceControlWritingStyle: JSONValue? = nil public var worktreesDirectory: String? = nil + public var addProjectBaseDirectory: String? = nil public var previousWorktreesDirectories: [String]? = nil public var removeAgentCreditsOnMerge: Bool? = nil public var sourceControlHosts: [String: [String: JSONValue]]? = nil @@ -310,6 +311,7 @@ public struct ServerSettingsSnapshot: Codable, Equatable, Sendable { case environmentIcon case sourceControlWritingStyle case worktreesDirectory, previousWorktreesDirectories, removeAgentCreditsOnMerge, sourceControlHosts + case addProjectBaseDirectory case worktreeSubmodules, storageCleanup, worktreeCleanup case defaultAutoPull, responseStreamingMode, projectSettingsOverrides, projectSettingsFolded, providerInstances case favorites, providerModelPreferences @@ -335,6 +337,7 @@ public struct ServerSettingsSnapshot: Codable, Equatable, Sendable { environmentIcon = try container.decodeIfPresent(String.self, forKey: .environmentIcon) sourceControlWritingStyle = try container.decodeIfPresent(JSONValue.self, forKey: .sourceControlWritingStyle) worktreesDirectory = try container.decodeIfPresent(String.self, forKey: .worktreesDirectory) + addProjectBaseDirectory = try container.decodeIfPresent(String.self, forKey: .addProjectBaseDirectory) previousWorktreesDirectories = try container.decodeIfPresent([String].self, forKey: .previousWorktreesDirectories) removeAgentCreditsOnMerge = try container.decodeIfPresent(Bool.self, forKey: .removeAgentCreditsOnMerge) sourceControlHosts = try container.decodeIfPresent([String: [String: JSONValue]].self, forKey: .sourceControlHosts) diff --git a/apps/swift-ios/Features/Shared/FeatureModels.swift b/apps/swift-ios/Features/Shared/FeatureModels.swift index 3b0d31b9022e7..b37f37f47e564 100644 --- a/apps/swift-ios/Features/Shared/FeatureModels.swift +++ b/apps/swift-ios/Features/Shared/FeatureModels.swift @@ -51,6 +51,7 @@ public struct FeatureEnvironment: Identifiable, Sendable, Equatable, Hashable, C public var machineIcon: String? = nil public var supportsScratch: Bool? = nil public var newProjectsRoot: String? = nil + public var addProjectBaseDirectory: String? = nil public var canCustomizeIcon: Bool? = nil public var supportsWorktreesDirectory: Bool? = nil public var supportsServerBrowser: Bool? = nil @@ -97,7 +98,7 @@ public struct FeatureEnvironment: Identifiable, Sendable, Equatable, Hashable, C case connectionState case connectionDetail case machineIcon - case supportsScratch, newProjectsRoot + case supportsScratch, newProjectsRoot, addProjectBaseDirectory case canCustomizeIcon, supportsWorktreesDirectory, supportsServerBrowser, permissions } @@ -116,6 +117,7 @@ public struct FeatureEnvironment: Identifiable, Sendable, Equatable, Hashable, C connectionDetail = try container.decodeIfPresent(String.self, forKey: .connectionDetail) machineIcon = try container.decodeIfPresent(String.self, forKey: .machineIcon) newProjectsRoot = try container.decodeIfPresent(String.self, forKey: .newProjectsRoot) + addProjectBaseDirectory = try container.decodeIfPresent(String.self, forKey: .addProjectBaseDirectory) supportsScratch = try container.decodeIfPresent(Bool.self, forKey: .supportsScratch) canCustomizeIcon = try container.decodeIfPresent(Bool.self, forKey: .canCustomizeIcon) supportsWorktreesDirectory = try container.decodeIfPresent(Bool.self, forKey: .supportsWorktreesDirectory) diff --git a/apps/swift-ios/Features/Workspace/AddProjectView.swift b/apps/swift-ios/Features/Workspace/AddProjectView.swift index 69ab5d090c2fb..8cbaceeff8ca1 100644 --- a/apps/swift-ios/Features/Workspace/AddProjectView.swift +++ b/apps/swift-ios/Features/Workspace/AddProjectView.swift @@ -631,7 +631,8 @@ public struct AddProjectView: View { private func resetEnvironmentState() { if mode == .newProject && selectedEnvironment?.newProjectsRoot == nil { mode = .folder } publishesToGitHub = false - browsePath = "~/" + let baseDirectory = addProjectBaseDirectory + browsePath = baseDirectory browseResult = nil browseError = nil browseRequestID = nil @@ -644,13 +645,20 @@ public struct AddProjectView: View { pendingManagedClone = nil cloneRequestID = nil didEditDestination = false - localPath = "~/" + localPath = baseDirectory destinationPath = repositoryInput.isEmpty - ? "~/" - : ProjectCreationPath.appending(repositoryName, to: "~/") + ? baseDirectory + : ProjectCreationPath.appending(repositoryName, to: baseDirectory) errorMessage = nil } + /// Matches web: a blank per-environment setting falls back to the home directory. + private var addProjectBaseDirectory: String { + let configured = selectedEnvironment?.addProjectBaseDirectory? + .trimmingCharacters(in: .whitespacesAndNewlines) ?? "" + return configured.isEmpty ? "~/" : ProjectCreationPath.directoryBrowsePath(configured) + } + private func loadDiscovery() async { guard let environmentID = selectedEnvironmentID, let projectClient else { From d3be7b93785ccd1227374f6daf6f2222e8dcf896 Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Thu, 22 Oct 2026 00:40:12 +0000 Subject: [PATCH 2/3] fix(swift-ios): follow a late Add Project base directory until the user edits paths A connected environment can publish its shell before its server config, so the Add Project sheet may seed ~/ before addProjectBaseDirectory arrives. Re-seed folder, browse, and clone paths when the setting lands, as long as the user has not browsed, edited a path, or started a clone. Co-Authored-By: Claude Opus 5.5 --- .../Features/Workspace/AddProjectView.swift | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/apps/swift-ios/Features/Workspace/AddProjectView.swift b/apps/swift-ios/Features/Workspace/AddProjectView.swift index 8cbaceeff8ca1..570009cfef809 100644 --- a/apps/swift-ios/Features/Workspace/AddProjectView.swift +++ b/apps/swift-ios/Features/Workspace/AddProjectView.swift @@ -49,6 +49,8 @@ public struct AddProjectView: View { @State private var isBrowsing = false @State private var browseError: String? @State private var browseRequestID: UUID? + /// The base directory seeded for an environment, kept until the user browses. + @State private var seededBaseDirectory: (environmentID: String, path: String)? @State private var discovery: SourceControlDiscoveryResult? @State private var isDiscovering = false @@ -154,6 +156,9 @@ public struct AddProjectView: View { updateSuggestedDestination() errorMessage = nil } + .onChange(of: addProjectBaseDirectory) { + reseedLateBaseDirectory() + } .task(id: selectedEnvironmentID) { guard selectedEnvironmentID != nil else { return } resetEnvironmentState() @@ -632,6 +637,7 @@ public struct AddProjectView: View { if mode == .newProject && selectedEnvironment?.newProjectsRoot == nil { mode = .folder } publishesToGitHub = false let baseDirectory = addProjectBaseDirectory + seededBaseDirectory = selectedEnvironmentID.map { ($0, baseDirectory) } browsePath = baseDirectory browseResult = nil browseError = nil @@ -652,6 +658,31 @@ public struct AddProjectView: View { errorMessage = nil } + /// A connected environment can publish its shell before its config, so the + /// setting may arrive after seeding. Follow it only while the paths are untouched. + private func reseedLateBaseDirectory() { + let baseDirectory = addProjectBaseDirectory + guard selectedEnvironment != nil, + let seeded = seededBaseDirectory, + seeded.environmentID == selectedEnvironmentID, + seeded.path != baseDirectory, + localPath == seeded.path, + !didEditDestination, + !isSubmitting, + cloneRequestID == nil, + pendingCloneRegistration == nil, + pendingManagedClone == nil else { + return + } + seededBaseDirectory = (seeded.environmentID, baseDirectory) + browsePath = baseDirectory + localPath = baseDirectory + destinationPath = repositoryInput.isEmpty + ? baseDirectory + : ProjectCreationPath.appending(repositoryName, to: baseDirectory) + Task { await loadDirectory(baseDirectory, updateSelection: false) } + } + /// Matches web: a blank per-environment setting falls back to the home directory. private var addProjectBaseDirectory: String { let configured = selectedEnvironment?.addProjectBaseDirectory? @@ -703,6 +734,7 @@ public struct AddProjectView: View { } let requestedPath = path.trimmingCharacters(in: .whitespacesAndNewlines) guard !requestedPath.isEmpty else { return } + if updateSelection { seededBaseDirectory = nil } let requestID = UUID() browseRequestID = requestID isBrowsing = true From 7a5fcf45e1f6df8fd5b1b1c42545ed76c9135b2f Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Thu, 22 Oct 2026 00:48:17 +0000 Subject: [PATCH 3/3] fix(swift-ios): reseed a late Add Project base directory after repository lookup Co-Authored-By: Claude Opus 5.5 --- apps/swift-ios/Features/Workspace/AddProjectView.swift | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/apps/swift-ios/Features/Workspace/AddProjectView.swift b/apps/swift-ios/Features/Workspace/AddProjectView.swift index 570009cfef809..4c696d5b76d5a 100644 --- a/apps/swift-ios/Features/Workspace/AddProjectView.swift +++ b/apps/swift-ios/Features/Workspace/AddProjectView.swift @@ -899,7 +899,11 @@ public struct AddProjectView: View { errorMessage = nil isSubmitting = true - defer { isSubmitting = false } + defer { + isSubmitting = false + // The base directory may have arrived while this lookup blocked reseeding. + reseedLateBaseDirectory() + } do { let result = try await projectClient.lookupProjectRepository( environmentID: environment.id,