From 5142cf1eb6eed2be906da8cdf1fe2265600eb6d8 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 20 Jun 2025 05:44:46 +0200 Subject: [PATCH] fix(ios): Fix iOS session creation and file browser issues - Fix working directory validation to fall back to home directory when iOS sends invalid paths - Fix file browser API client to handle correct response format (absolutePath + files) - Improve session creation header design with translucent material and fixed height - Add authentication support placeholder for future API protection - Add better error logging for debugging API issues - Move TerminalTextFieldStyle to SessionCreateView for better organization The iOS app can now create sessions successfully and browse directories without errors. --- ios/VibeTunnel.xcodeproj/project.pbxproj | 6 +- ios/VibeTunnel/Services/APIClient.swift | 32 ++++++++++- .../Views/Connection/ServerConfigForm.swift | 18 ------ ios/VibeTunnel/Views/FileBrowserView.swift | 9 ++- .../Views/Sessions/SessionCreateView.swift | 56 ++++++++++++++++--- 5 files changed, 85 insertions(+), 36 deletions(-) diff --git a/ios/VibeTunnel.xcodeproj/project.pbxproj b/ios/VibeTunnel.xcodeproj/project.pbxproj index fcf67c98..1b4ab5fb 100644 --- a/ios/VibeTunnel.xcodeproj/project.pbxproj +++ b/ios/VibeTunnel.xcodeproj/project.pbxproj @@ -97,7 +97,7 @@ attributes = { BuildIndependentTargetsInParallel = YES; LastSwiftUpdateCheck = 1600; - LastUpgradeCheck = 1600; + LastUpgradeCheck = 2600; TargetAttributes = { 788687F02DFF4FCB00B22C15 = { CreatedOnToolsVersion = 16.0; @@ -205,6 +205,7 @@ MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; + STRING_CATALOG_GENERATE_SYMBOLS = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; @@ -329,6 +330,7 @@ MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = iphoneos; + STRING_CATALOG_GENERATE_SYMBOLS = YES; SWIFT_COMPILATION_MODE = wholemodule; VALIDATE_PRODUCT = YES; }; @@ -377,4 +379,4 @@ /* End XCSwiftPackageProductDependency section */ }; rootObject = 0DD83B4196D66608380E46BA /* Project object */; -} \ No newline at end of file +} diff --git a/ios/VibeTunnel/Services/APIClient.swift b/ios/VibeTunnel/Services/APIClient.swift index 735231d0..c362d357 100644 --- a/ios/VibeTunnel/Services/APIClient.swift +++ b/ios/VibeTunnel/Services/APIClient.swift @@ -283,9 +283,15 @@ class APIClient: APIClientProtocol { } } + private func addAuthenticationIfNeeded(_ request: inout URLRequest) { + // For now, we don't have authentication configured in the iOS app + // This is a placeholder for future authentication support + // The server might be running without password protection + } + // MARK: - File System Operations - func browseDirectory(path: String) async throws -> [FileEntry] { + func browseDirectory(path: String) async throws -> (absolutePath: String, files: [FileEntry]) { guard let baseURL = baseURL else { throw APIError.noServerConfigured } @@ -300,11 +306,31 @@ class APIClient: APIClientProtocol { var request = URLRequest(url: url) request.httpMethod = "GET" + // Add authentication header if needed + addAuthenticationIfNeeded(&request) + let (data, response) = try await session.data(for: request) + + // Log response for debugging + if let httpResponse = response as? HTTPURLResponse { + print("[APIClient] Browse directory response: \(httpResponse.statusCode)") + if httpResponse.statusCode >= 400 { + if let errorString = String(data: data, encoding: .utf8) { + print("[APIClient] Error response body: \(errorString)") + } + } + } + try validateResponse(response) - let entries = try decoder.decode([FileEntry].self, from: data) - return entries + // Decode the response which includes absolutePath and files + struct BrowseResponse: Codable { + let absolutePath: String + let files: [FileEntry] + } + + let browseResponse = try decoder.decode(BrowseResponse.self, from: data) + return (absolutePath: browseResponse.absolutePath, files: browseResponse.files) } func createDirectory(path: String) async throws { diff --git a/ios/VibeTunnel/Views/Connection/ServerConfigForm.swift b/ios/VibeTunnel/Views/Connection/ServerConfigForm.swift index f1f95763..ddc50591 100644 --- a/ios/VibeTunnel/Views/Connection/ServerConfigForm.swift +++ b/ios/VibeTunnel/Views/Connection/ServerConfigForm.swift @@ -182,21 +182,3 @@ struct ServerConfigForm: View { } } } - -// Custom text field style -struct TerminalTextFieldStyle: TextFieldStyle { - func _body(configuration: TextField) -> some View { - configuration - .font(Theme.Typography.terminalSystem(size: 16)) - .foregroundColor(Theme.Colors.terminalForeground) - .padding(Theme.Spacing.md) - .background( - RoundedRectangle(cornerRadius: Theme.CornerRadius.medium) - .fill(Theme.Colors.cardBackground) - ) - .overlay( - RoundedRectangle(cornerRadius: Theme.CornerRadius.medium) - .stroke(Theme.Colors.primaryAccent.opacity(0.3), lineWidth: 1) - ) - } -} \ No newline at end of file diff --git a/ios/VibeTunnel/Views/FileBrowserView.swift b/ios/VibeTunnel/Views/FileBrowserView.swift index 555b6275..4274fbb7 100644 --- a/ios/VibeTunnel/Views/FileBrowserView.swift +++ b/ios/VibeTunnel/Views/FileBrowserView.swift @@ -297,12 +297,11 @@ class FileBrowserViewModel: ObservableObject { defer { isLoading = false } do { - let fetchedEntries = try await apiClient.browseDirectory(path: path) - // For now, we'll use the requested path as the current path - // The Go server doesn't return the absolute path in the response - currentPath = path + let result = try await apiClient.browseDirectory(path: path) + // Use the absolute path returned by the server + currentPath = result.absolutePath withAnimation(.easeInOut(duration: 0.2)) { - entries = fetchedEntries + entries = result.files } } catch { print("[FileBrowser] Failed to load directory: \(error)") diff --git a/ios/VibeTunnel/Views/Sessions/SessionCreateView.swift b/ios/VibeTunnel/Views/Sessions/SessionCreateView.swift index 3014a31a..38ae0827 100644 --- a/ios/VibeTunnel/Views/Sessions/SessionCreateView.swift +++ b/ios/VibeTunnel/Views/Sessions/SessionCreateView.swift @@ -1,5 +1,23 @@ import SwiftUI +// Custom text field style for terminal-like appearance +struct TerminalTextFieldStyle: TextFieldStyle { + func _body(configuration: TextField) -> some View { + configuration + .font(Theme.Typography.terminalSystem(size: 16)) + .foregroundColor(Theme.Colors.terminalForeground) + .padding() + .background( + RoundedRectangle(cornerRadius: Theme.CornerRadius.small) + .fill(Theme.Colors.cardBackground) + ) + .overlay( + RoundedRectangle(cornerRadius: Theme.CornerRadius.small) + .stroke(Theme.Colors.cardBorder, lineWidth: 1) + ) + } +} + struct SessionCreateView: View { @Binding var isPresented: Bool let onCreated: (String) -> Void @@ -208,14 +226,29 @@ struct SessionCreateView: View { .navigationBarHidden(true) .safeAreaInset(edge: .top) { // Custom Navigation Bar with proper safe area handling - VStack(spacing: 0) { + ZStack { + // Background with blur and transparency + Rectangle() + .fill(.ultraThinMaterial) + .background(Theme.Colors.terminalBackground.opacity(0.5)) + + // Content HStack { - Button("Cancel") { + Button(action: { HapticFeedback.impact(.light) isPresented = false + }) { + Text("Cancel") + .font(.system(size: 17)) + .foregroundColor(Theme.Colors.errorAccent) } - .font(.system(size: 17)) - .foregroundColor(Theme.Colors.errorAccent) + .buttonStyle(PlainButtonStyle()) + + Spacer() + + Text("New Session") + .font(.system(size: 17, weight: .semibold)) + .foregroundColor(Theme.Colors.terminalForeground) Spacer() @@ -230,16 +263,23 @@ struct SessionCreateView: View { } else { Text("Create") .font(.system(size: 17, weight: .semibold)) + .foregroundColor(command.isEmpty ? Theme.Colors.primaryAccent.opacity(0.5) : Theme.Colors.primaryAccent) } } - .foregroundColor(Theme.Colors.primaryAccent) + .buttonStyle(PlainButtonStyle()) .disabled(isCreating || command.isEmpty) } - .padding(.horizontal) + .padding(.horizontal, 20) .padding(.vertical, 16) - .frame(maxWidth: .infinity) - .background(.ultraThinMaterial.opacity(0.8)) } + .frame(height: 56) // Fixed height for the header + .overlay( + // Subtle bottom border + Rectangle() + .fill(Theme.Colors.cardBorder.opacity(0.15)) + .frame(height: 0.5), + alignment: .bottom + ) } .onAppear { loadDefaults()