mirror of
https://github.com/samsonjs/vibetunnel.git
synced 2026-04-27 15:17:38 +00:00
fix: CI and linting issues across all platforms
- Fix code signing in Mac and iOS test workflows - Fix all SwiftFormat and SwiftLint issues - Fix ESLint issues in web code - Remove force casts and unwrapping in Swift code - Update build scripts to use correct file paths
This commit is contained in:
parent
d72b009696
commit
baaaa5a033
61 changed files with 685 additions and 609 deletions
3
.github/workflows/mac.yml
vendored
3
.github/workflows/mac.yml
vendored
|
|
@ -206,6 +206,9 @@ jobs:
|
|||
-scheme VibeTunnel-Mac \
|
||||
-configuration Debug \
|
||||
-destination "platform=macOS" \
|
||||
CODE_SIGN_IDENTITY="" \
|
||||
CODE_SIGNING_REQUIRED=NO \
|
||||
CODE_SIGNING_ALLOWED=NO \
|
||||
| xcbeautify || {
|
||||
echo "::error::Tests failed"
|
||||
exit 1
|
||||
|
|
|
|||
|
|
@ -45,7 +45,8 @@ struct VibeTunnelApp: App {
|
|||
|
||||
if url.host == "session",
|
||||
let sessionId = url.pathComponents.last,
|
||||
!sessionId.isEmpty {
|
||||
!sessionId.isEmpty
|
||||
{
|
||||
navigationManager.navigateToSession(sessionId)
|
||||
}
|
||||
}
|
||||
|
|
@ -75,7 +76,8 @@ class ConnectionManager {
|
|||
|
||||
private func loadSavedConnection() {
|
||||
if let data = UserDefaults.standard.data(forKey: "savedServerConfig"),
|
||||
let config = try? JSONDecoder().decode(ServerConfig.self, from: data) {
|
||||
let config = try? JSONDecoder().decode(ServerConfig.self, from: data)
|
||||
{
|
||||
self.serverConfig = config
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -181,7 +181,8 @@ class CastRecorder {
|
|||
let eventArray: [Any] = [event.time, event.type, event.data]
|
||||
|
||||
if let jsonData = try? JSONSerialization.data(withJSONObject: eventArray),
|
||||
let jsonString = String(data: jsonData, encoding: .utf8) {
|
||||
let jsonString = String(data: jsonData, encoding: .utf8)
|
||||
{
|
||||
castContent += jsonString + "\n"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,16 +37,16 @@ struct ServerProfile: Identifiable, Codable, Equatable {
|
|||
/// Create a ServerConfig from this profile
|
||||
func toServerConfig(password: String? = nil) -> ServerConfig? {
|
||||
guard let urlComponents = URLComponents(string: url),
|
||||
let host = urlComponents.host else {
|
||||
let host = urlComponents.host
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Determine default port based on scheme
|
||||
let defaultPort: Int
|
||||
if let scheme = urlComponents.scheme?.lowercased() {
|
||||
defaultPort = scheme == "https" ? 443 : 80
|
||||
let defaultPort: Int = if let scheme = urlComponents.scheme?.lowercased() {
|
||||
scheme == "https" ? 443 : 80
|
||||
} else {
|
||||
defaultPort = 80
|
||||
80
|
||||
}
|
||||
|
||||
let port = urlComponents.port ?? defaultPort
|
||||
|
|
@ -68,7 +68,8 @@ extension ServerProfile {
|
|||
/// Load all saved profiles from UserDefaults
|
||||
static func loadAll() -> [ServerProfile] {
|
||||
guard let data = UserDefaults.standard.data(forKey: storageKey),
|
||||
let profiles = try? JSONDecoder().decode([ServerProfile].self, from: data) else {
|
||||
let profiles = try? JSONDecoder().decode([ServerProfile].self, from: data)
|
||||
else {
|
||||
return []
|
||||
}
|
||||
return profiles
|
||||
|
|
@ -117,7 +118,8 @@ extension ServerProfile {
|
|||
|
||||
static func suggestedName(for url: String) -> String {
|
||||
if let urlComponents = URLComponents(string: url),
|
||||
let host = urlComponents.host {
|
||||
let host = urlComponents.host
|
||||
{
|
||||
// Remove common suffixes
|
||||
let cleanHost = host
|
||||
.replacingOccurrences(of: ".local", with: "")
|
||||
|
|
|
|||
|
|
@ -45,7 +45,8 @@ enum TerminalEvent {
|
|||
let exitString = array[0] as? String,
|
||||
exitString == "exit",
|
||||
let exitCode = array[1] as? Int,
|
||||
let sessionId = array[2] as? String {
|
||||
let sessionId = array[2] as? String
|
||||
{
|
||||
self = .exit(code: exitCode, sessionId: sessionId)
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,8 @@ enum TerminalRenderer: String, CaseIterable, Codable {
|
|||
static var selected: Self {
|
||||
get {
|
||||
if let rawValue = UserDefaults.standard.string(forKey: "selectedTerminalRenderer"),
|
||||
let renderer = Self(rawValue: rawValue) {
|
||||
let renderer = Self(rawValue: rawValue)
|
||||
{
|
||||
return renderer
|
||||
}
|
||||
return .swiftTerm // Default
|
||||
|
|
|
|||
|
|
@ -384,7 +384,8 @@ class APIClient: APIClientProtocol {
|
|||
// This is the header
|
||||
if let version = json["version"] as? Int,
|
||||
let width = json["width"] as? Int,
|
||||
let height = json["height"] as? Int {
|
||||
let height = json["height"] as? Int
|
||||
{
|
||||
header = AsciinemaHeader(
|
||||
version: version,
|
||||
width: width,
|
||||
|
|
@ -401,7 +402,8 @@ class APIClient: APIClientProtocol {
|
|||
if json.count >= 3,
|
||||
let timestamp = json[0] as? Double,
|
||||
let typeStr = json[1] as? String,
|
||||
let eventData = json[2] as? String {
|
||||
let eventData = json[2] as? String
|
||||
{
|
||||
let eventType: AsciinemaEvent.EventType
|
||||
switch typeStr {
|
||||
case "o": eventType = .output
|
||||
|
|
@ -479,7 +481,8 @@ class APIClient: APIClientProtocol {
|
|||
showHidden: Bool = false,
|
||||
gitFilter: String = "all"
|
||||
)
|
||||
async throws -> DirectoryListing {
|
||||
async throws -> DirectoryListing
|
||||
{
|
||||
guard let baseURL else {
|
||||
throw APIError.noServerConfigured
|
||||
}
|
||||
|
|
|
|||
|
|
@ -114,7 +114,8 @@ class BufferWebSocketClient: NSObject {
|
|||
// Add authentication header if needed
|
||||
if let config = UserDefaults.standard.data(forKey: "savedServerConfig"),
|
||||
let serverConfig = try? JSONDecoder().decode(ServerConfig.self, from: config),
|
||||
let authHeader = serverConfig.authorizationHeader {
|
||||
let authHeader = serverConfig.authorizationHeader
|
||||
{
|
||||
headers["Authorization"] = authHeader
|
||||
}
|
||||
|
||||
|
|
@ -211,7 +212,8 @@ class BufferWebSocketClient: NSObject {
|
|||
|
||||
// Decode terminal event
|
||||
if let event = decodeTerminalEvent(from: messageData),
|
||||
let handler = subscriptions[sessionId] {
|
||||
let handler = subscriptions[sessionId]
|
||||
{
|
||||
logger.verbose("Dispatching event to handler")
|
||||
handler(event)
|
||||
} else {
|
||||
|
|
@ -598,14 +600,16 @@ class BufferWebSocketClient: NSObject {
|
|||
(0xFF00...0xFF60).contains(value) || // Fullwidth Forms
|
||||
(0xFFE0...0xFFE6).contains(value) || // Fullwidth Forms
|
||||
(0x20000...0x2FFFD).contains(value) || // CJK Extension B-F
|
||||
(0x30000...0x3FFFD).contains(value) { // CJK Extension G
|
||||
(0x30000...0x3FFFD).contains(value)
|
||||
{ // CJK Extension G
|
||||
return 2
|
||||
}
|
||||
|
||||
// Zero-width characters
|
||||
if (0x200B...0x200F).contains(value) || // Zero-width spaces
|
||||
(0xFE00...0xFE0F).contains(value) || // Variation selectors
|
||||
scalar.properties.isJoinControl {
|
||||
scalar.properties.isJoinControl
|
||||
{
|
||||
return 0
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -80,7 +80,8 @@ enum KeychainService {
|
|||
}
|
||||
|
||||
guard let data = result as? Data,
|
||||
let password = String(data: data, encoding: .utf8) else {
|
||||
let password = String(data: data, encoding: .utf8)
|
||||
else {
|
||||
throw KeychainError.unexpectedData
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -75,7 +75,8 @@ final class LivePreviewManager {
|
|||
|
||||
// Schedule delayed update if not already scheduled
|
||||
if self.updateTimers[sessionId] == nil {
|
||||
let timer = Timer.scheduledTimer(withTimeInterval: self.updateInterval, repeats: false) { _ in
|
||||
let timer = Timer
|
||||
.scheduledTimer(withTimeInterval: self.updateInterval, repeats: false) { _ in
|
||||
Task { @MainActor in
|
||||
if let pending = pendingSnapshot {
|
||||
subscription.latestSnapshot = pending
|
||||
|
|
@ -173,7 +174,7 @@ struct LivePreviewModifier: ViewModifier {
|
|||
}
|
||||
}
|
||||
|
||||
// Environment key for passing subscription down the view hierarchy
|
||||
/// Environment key for passing subscription down the view hierarchy
|
||||
private struct LivePreviewSubscriptionKey: EnvironmentKey {
|
||||
static let defaultValue: LivePreviewSubscription? = nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -84,7 +84,8 @@ class QuickLookManager: NSObject, ObservableObject {
|
|||
|
||||
for file in files {
|
||||
if let creationDate = try? file.resourceValues(forKeys: [.creationDateKey]).creationDate,
|
||||
creationDate < oneHourAgo {
|
||||
creationDate < oneHourAgo
|
||||
{
|
||||
try? FileManager.default.removeItem(at: file)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -111,7 +111,13 @@ class ReconnectionManager {
|
|||
|
||||
extension ReconnectionManager {
|
||||
/// Calculate the next retry delay using exponential backoff
|
||||
static func calculateBackoff(attempt: Int, baseDelay: TimeInterval = 1.0, maxDelay: TimeInterval = 60.0) -> TimeInterval {
|
||||
static func calculateBackoff(
|
||||
attempt: Int,
|
||||
baseDelay: TimeInterval = 1.0,
|
||||
maxDelay: TimeInterval = 60.0
|
||||
)
|
||||
-> TimeInterval
|
||||
{
|
||||
let exponentialDelay = baseDelay * pow(2.0, Double(attempt - 1))
|
||||
return min(exponentialDelay, maxDelay)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -117,13 +117,15 @@ final class SSEClient: NSObject, @unchecked Sendable {
|
|||
// Check for exit event
|
||||
if let firstElement = array[0] as? String, firstElement == "exit",
|
||||
let exitCode = array[1] as? Int,
|
||||
let sessionId = array[2] as? String {
|
||||
let sessionId = array[2] as? String
|
||||
{
|
||||
delegate?.sseClient(self, didReceiveEvent: .exit(exitCode: exitCode, sessionId: sessionId))
|
||||
}
|
||||
// Regular terminal output
|
||||
else if let timestamp = array[0] as? Double,
|
||||
let type = array[1] as? String,
|
||||
let outputData = array[2] as? String {
|
||||
let outputData = array[2] as? String
|
||||
{
|
||||
delegate?.sseClient(
|
||||
self,
|
||||
didReceiveEvent: .terminalOutput(timestamp: timestamp, type: type, data: outputData)
|
||||
|
|
|
|||
|
|
@ -29,7 +29,9 @@ extension View {
|
|||
func errorAlert(
|
||||
error: Binding<Error?>,
|
||||
onDismiss: (() -> Void)? = nil
|
||||
) -> some View {
|
||||
)
|
||||
-> some View
|
||||
{
|
||||
modifier(ErrorAlertModifier(error: error, onDismiss: onDismiss))
|
||||
}
|
||||
}
|
||||
|
|
@ -70,22 +72,22 @@ extension APIError: RecoverableError {
|
|||
var recoverySuggestion: String? {
|
||||
switch self {
|
||||
case .noServerConfigured:
|
||||
return "Please configure a server connection in Settings."
|
||||
"Please configure a server connection in Settings."
|
||||
case .networkError:
|
||||
return "Check your internet connection and try again."
|
||||
"Check your internet connection and try again."
|
||||
case .serverError(let code, _):
|
||||
switch code {
|
||||
case 401:
|
||||
return "Check your authentication credentials in Settings."
|
||||
"Check your authentication credentials in Settings."
|
||||
case 500...599:
|
||||
return "The server is experiencing issues. Please try again later."
|
||||
"The server is experiencing issues. Please try again later."
|
||||
default:
|
||||
return nil
|
||||
nil
|
||||
}
|
||||
case .resizeDisabledByServer:
|
||||
return "Terminal resizing is not supported by this server."
|
||||
"Terminal resizing is not supported by this server."
|
||||
default:
|
||||
return nil
|
||||
nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -151,7 +153,9 @@ extension Task where Failure == Error {
|
|||
priority: TaskPriority? = nil,
|
||||
errorHandler: @escaping @Sendable (Error) -> Void,
|
||||
operation: @escaping @Sendable () async throws -> T
|
||||
) -> Task<T, Error> {
|
||||
)
|
||||
-> Task<T, Error>
|
||||
{
|
||||
Task<T, Error>(priority: priority) {
|
||||
do {
|
||||
return try await operation()
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import SwiftUI
|
||||
#if targetEnvironment(macCatalyst)
|
||||
import UIKit
|
||||
import Dynamic
|
||||
import UIKit
|
||||
|
||||
// MARK: - Window Style
|
||||
|
||||
|
|
@ -61,8 +61,9 @@ class MacCatalystWindowManager: ObservableObject {
|
|||
}
|
||||
|
||||
private func applyWindowStyle(_ style: MacWindowStyle) {
|
||||
guard let window = window,
|
||||
let nsWindow = window.nsWindow else {
|
||||
guard let window,
|
||||
let nsWindow = window.nsWindow
|
||||
else {
|
||||
logger.warning("Unable to access NSWindow")
|
||||
return
|
||||
}
|
||||
|
|
@ -83,7 +84,12 @@ class MacCatalystWindowManager: ObservableObject {
|
|||
// Show title bar
|
||||
nsWindow.titlebarAppearsTransparent = false
|
||||
nsWindow.titleVisibility = Dynamic.NSWindowTitleVisibility.visible
|
||||
nsWindow.styleMask = nsWindow.styleMask.asObject! as! UInt | Dynamic.NSWindowStyleMask.titled.asObject! as! UInt
|
||||
guard let currentMask = nsWindow.styleMask.asObject as? UInt,
|
||||
let titledMask = Dynamic.NSWindowStyleMask.titled.asObject as? UInt else {
|
||||
logger.error("Failed to get window style masks")
|
||||
return
|
||||
}
|
||||
nsWindow.styleMask = currentMask | titledMask
|
||||
|
||||
// Reset traffic light positions
|
||||
resetTrafficLightPositions(nsWindow)
|
||||
|
|
@ -104,8 +110,12 @@ class MacCatalystWindowManager: ObservableObject {
|
|||
nsWindow.backgroundColor = Dynamic.NSColor.clearColor
|
||||
|
||||
// Keep the titled style mask to preserve traffic lights
|
||||
let currentMask = nsWindow.styleMask.asObject! as! UInt
|
||||
nsWindow.styleMask = currentMask | Dynamic.NSWindowStyleMask.titled.asObject! as! UInt
|
||||
guard let currentMask = nsWindow.styleMask.asObject as? UInt,
|
||||
let titledMask = Dynamic.NSWindowStyleMask.titled.asObject as? UInt else {
|
||||
logger.error("Failed to get window style masks")
|
||||
return
|
||||
}
|
||||
nsWindow.styleMask = currentMask | titledMask
|
||||
|
||||
// Reposition traffic lights
|
||||
repositionTrafficLights(nsWindow, window: window)
|
||||
|
|
@ -164,8 +174,13 @@ class MacCatalystWindowManager: ObservableObject {
|
|||
|
||||
// Add new tracking area at the button's current position
|
||||
let trackingRect = button.bounds
|
||||
let options = Dynamic.NSTrackingAreaOptions.mouseEnteredAndExited.asObject! as! UInt |
|
||||
Dynamic.NSTrackingAreaOptions.activeAlways.asObject! as! UInt
|
||||
guard let mouseEnteredAndExited = Dynamic.NSTrackingAreaOptions.mouseEnteredAndExited.asObject as? UInt,
|
||||
let activeAlways = Dynamic.NSTrackingAreaOptions.activeAlways.asObject as? UInt
|
||||
else {
|
||||
logger.error("Failed to get tracking area options")
|
||||
return
|
||||
}
|
||||
let options = mouseEnteredAndExited | activeAlways
|
||||
|
||||
let trackingArea = Dynamic.NSTrackingArea.alloc()
|
||||
.initWithRect(trackingRect, options: options, owner: button, userInfo: nil)
|
||||
|
|
@ -189,7 +204,7 @@ class MacCatalystWindowManager: ObservableObject {
|
|||
object: nil,
|
||||
queue: .main
|
||||
) { [weak self] notification in
|
||||
guard let self = self,
|
||||
guard let self,
|
||||
self.windowStyle == .inline,
|
||||
let window = self.window,
|
||||
let notificationWindow = notification.object as? NSObject,
|
||||
|
|
@ -208,7 +223,7 @@ class MacCatalystWindowManager: ObservableObject {
|
|||
object: window,
|
||||
queue: .main
|
||||
) { [weak self] _ in
|
||||
guard let self = self,
|
||||
guard let self,
|
||||
self.windowStyle == .inline else { return }
|
||||
|
||||
// Reapply inline style when window becomes key
|
||||
|
|
@ -223,12 +238,13 @@ class MacCatalystWindowManager: ObservableObject {
|
|||
object: nil,
|
||||
queue: .main
|
||||
) { [weak self] _ in
|
||||
guard let self = self,
|
||||
guard let self,
|
||||
self.windowStyle == .inline else { return }
|
||||
|
||||
// Reposition if needed
|
||||
if let window = self.window,
|
||||
let nsWindow = window.nsWindow {
|
||||
let nsWindow = window.nsWindow
|
||||
{
|
||||
self.repositionTrafficLights(Dynamic(nsWindow), window: window)
|
||||
}
|
||||
}
|
||||
|
|
@ -259,7 +275,8 @@ struct MacCatalystWindowStyle: ViewModifier {
|
|||
|
||||
private func setupWindow() {
|
||||
guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
|
||||
let window = windowScene.windows.first else {
|
||||
let window = windowScene.windows.first
|
||||
else {
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,13 +17,13 @@ class ServerProfilesViewModel {
|
|||
profiles = ServerProfile.loadAll().sorted { profile1, profile2 in
|
||||
// Sort by last connected (most recent first), then by name
|
||||
if let date1 = profile1.lastConnected, let date2 = profile2.lastConnected {
|
||||
return date1 > date2
|
||||
date1 > date2
|
||||
} else if profile1.lastConnected != nil {
|
||||
return true
|
||||
true
|
||||
} else if profile2.lastConnected != nil {
|
||||
return false
|
||||
false
|
||||
} else {
|
||||
return profile1.name < profile2.name
|
||||
profile1.name < profile2.name
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -32,7 +32,7 @@ class ServerProfilesViewModel {
|
|||
ServerProfile.save(profile)
|
||||
|
||||
// Save password to keychain if provided
|
||||
if let password = password, !password.isEmpty {
|
||||
if let password, !password.isEmpty {
|
||||
try KeychainService.savePassword(password, for: profile.id)
|
||||
}
|
||||
|
||||
|
|
@ -45,7 +45,7 @@ class ServerProfilesViewModel {
|
|||
ServerProfile.save(updatedProfile)
|
||||
|
||||
// Update password if provided
|
||||
if let password = password {
|
||||
if let password {
|
||||
if password.isEmpty {
|
||||
// Delete password if empty
|
||||
try KeychainService.deletePassword(for: profile.id)
|
||||
|
|
@ -140,7 +140,8 @@ extension ServerProfilesViewModel {
|
|||
|
||||
// Validate URL
|
||||
guard let url = URL(string: cleanURL),
|
||||
let _ = url.host else {
|
||||
let _ = url.host
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -121,7 +121,8 @@ class ConnectionViewModel {
|
|||
|
||||
func loadLastConnection() {
|
||||
if let config = UserDefaults.standard.data(forKey: "savedServerConfig"),
|
||||
let serverConfig = try? JSONDecoder().decode(ServerConfig.self, from: config) {
|
||||
let serverConfig = try? JSONDecoder().decode(ServerConfig.self, from: config)
|
||||
{
|
||||
self.host = serverConfig.host
|
||||
self.port = String(serverConfig.port)
|
||||
self.name = serverConfig.name ?? ""
|
||||
|
|
@ -162,7 +163,8 @@ class ConnectionViewModel {
|
|||
let (_, response) = try await URLSession.shared.data(for: request)
|
||||
|
||||
if let httpResponse = response as? HTTPURLResponse,
|
||||
httpResponse.statusCode == 200 {
|
||||
httpResponse.statusCode == 200
|
||||
{
|
||||
onSuccess(config)
|
||||
} else {
|
||||
errorMessage = "Failed to connect to server"
|
||||
|
|
|
|||
|
|
@ -426,7 +426,8 @@ struct ServerProfileEditView: View {
|
|||
.task {
|
||||
// Load existing password from keychain
|
||||
if profile.requiresAuth,
|
||||
let existingPassword = try? KeychainService.getPassword(for: profile.id) {
|
||||
let existingPassword = try? KeychainService.getPassword(for: profile.id)
|
||||
{
|
||||
password = existingPassword
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -218,7 +218,8 @@ struct ServerConfigForm: View {
|
|||
private func loadRecentServers() {
|
||||
// Load recent servers from UserDefaults
|
||||
if let data = UserDefaults.standard.data(forKey: "recentServers"),
|
||||
let servers = try? JSONDecoder().decode([ServerConfig].self, from: data) {
|
||||
let servers = try? JSONDecoder().decode([ServerConfig].self, from: data)
|
||||
{
|
||||
recentServers = servers
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -88,7 +88,8 @@ struct FilePreviewView: View {
|
|||
case .image:
|
||||
if let content = preview.content,
|
||||
let data = Data(base64Encoded: content),
|
||||
let uiImage = UIImage(data: data) {
|
||||
let uiImage = UIImage(data: data)
|
||||
{
|
||||
Image(uiImage: uiImage)
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
|
|
|
|||
|
|
@ -25,7 +25,8 @@ struct SessionCardView: View {
|
|||
// Convert absolute paths back to ~ notation for display
|
||||
let homePrefix = "/Users/"
|
||||
if session.workingDir.hasPrefix(homePrefix),
|
||||
let userEndIndex = session.workingDir[homePrefix.endIndex...].firstIndex(of: "/") {
|
||||
let userEndIndex = session.workingDir[homePrefix.endIndex...].firstIndex(of: "/")
|
||||
{
|
||||
let restOfPath = String(session.workingDir[userEndIndex...])
|
||||
return "~\(restOfPath)"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -200,7 +200,8 @@ struct SessionListView: View {
|
|||
.onChange(of: navigationManager.shouldNavigateToSession) { _, shouldNavigate in
|
||||
if shouldNavigate,
|
||||
let sessionId = navigationManager.selectedSessionId,
|
||||
let session = viewModel.sessions.first(where: { $0.id == sessionId }) {
|
||||
let session = viewModel.sessions.first(where: { $0.id == sessionId })
|
||||
{
|
||||
selectedSession = session
|
||||
navigationManager.clearNavigation()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -295,7 +295,8 @@ struct AdvancedSettingsView: View {
|
|||
|
||||
Text(macWindowStyle == .inline ?
|
||||
"Traffic light buttons appear inline with content" :
|
||||
"Standard macOS title bar with traffic lights")
|
||||
"Standard macOS title bar with traffic lights"
|
||||
)
|
||||
.font(Theme.Typography.terminalSystem(size: 12))
|
||||
.foregroundColor(Theme.Colors.terminalForeground.opacity(0.6))
|
||||
}
|
||||
|
|
@ -434,7 +435,7 @@ struct LinkRow: View {
|
|||
|
||||
var body: some View {
|
||||
Button(action: {
|
||||
if let url = url {
|
||||
if let url {
|
||||
UIApplication.shared.open(url)
|
||||
}
|
||||
}) {
|
||||
|
|
|
|||
|
|
@ -296,7 +296,8 @@ struct SystemLogsView: View {
|
|||
// Present it
|
||||
if let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene,
|
||||
let window = windowScene.windows.first,
|
||||
let rootVC = window.rootViewController {
|
||||
let rootVC = window.rootViewController
|
||||
{
|
||||
rootVC.present(activityVC, animated: true)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -233,7 +233,8 @@ struct CtrlKeyButton: View {
|
|||
Button(action: {
|
||||
// Calculate control character (Ctrl+A = 1, Ctrl+B = 2, etc.)
|
||||
if let scalar = char.unicodeScalars.first,
|
||||
let ctrlScalar = UnicodeScalar(scalar.value - 64) {
|
||||
let ctrlScalar = UnicodeScalar(scalar.value - 64)
|
||||
{
|
||||
let ctrlChar = Character(ctrlScalar)
|
||||
onPress(String(ctrlChar))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ struct CtrlKeyGrid: View {
|
|||
@Binding var isPresented: Bool
|
||||
let onKeyPress: (String) -> Void
|
||||
|
||||
// Common Ctrl combinations organized by category
|
||||
/// Common Ctrl combinations organized by category
|
||||
let navigationKeys = [
|
||||
("A", "Beginning of line"),
|
||||
("E", "End of line"),
|
||||
|
|
@ -106,11 +106,11 @@ struct CtrlKeyGrid: View {
|
|||
|
||||
private var currentKeys: [(String, String)] {
|
||||
switch selectedCategory {
|
||||
case 0: return navigationKeys
|
||||
case 1: return editingKeys
|
||||
case 2: return processKeys
|
||||
case 3: return searchKeys
|
||||
default: return navigationKeys
|
||||
case 0: navigationKeys
|
||||
case 1: editingKeys
|
||||
case 2: processKeys
|
||||
case 3: searchKeys
|
||||
default: navigationKeys
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,9 @@ struct QuickFontSizeButtons: View {
|
|||
Button(action: decreaseFontSize) {
|
||||
Image(systemName: "minus")
|
||||
.font(.system(size: 14, weight: .medium))
|
||||
.foregroundColor(fontSize > minSize ? Theme.Colors.primaryAccent : Theme.Colors.secondaryText.opacity(0.5))
|
||||
.foregroundColor(fontSize > minSize ? Theme.Colors.primaryAccent : Theme.Colors.secondaryText
|
||||
.opacity(0.5)
|
||||
)
|
||||
.frame(width: 30, height: 30)
|
||||
.background(Theme.Colors.cardBackground)
|
||||
.overlay(
|
||||
|
|
@ -41,7 +43,9 @@ struct QuickFontSizeButtons: View {
|
|||
Button(action: increaseFontSize) {
|
||||
Image(systemName: "plus")
|
||||
.font(.system(size: 14, weight: .medium))
|
||||
.foregroundColor(fontSize < maxSize ? Theme.Colors.primaryAccent : Theme.Colors.secondaryText.opacity(0.5))
|
||||
.foregroundColor(fontSize < maxSize ? Theme.Colors.primaryAccent : Theme.Colors.secondaryText
|
||||
.opacity(0.5)
|
||||
)
|
||||
.frame(width: 30, height: 30)
|
||||
.background(Theme.Colors.cardBackground)
|
||||
.overlay(
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@ struct TerminalBufferPreview: View {
|
|||
}
|
||||
|
||||
// Check if RGB color (has alpha channel flag)
|
||||
if (fg & 0xFF000000) != 0 {
|
||||
if (fg & 0xFF00_0000) != 0 {
|
||||
// RGB color
|
||||
let red = Double((fg >> 16) & 0xFF) / 255.0
|
||||
let green = Double((fg >> 8) & 0xFF) / 255.0
|
||||
|
|
@ -86,7 +86,7 @@ struct TerminalBufferPreview: View {
|
|||
}
|
||||
|
||||
// Check if RGB color (has alpha channel flag)
|
||||
if (bg & 0xFF000000) != 0 {
|
||||
if (bg & 0xFF00_0000) != 0 {
|
||||
// RGB color
|
||||
let red = Double((bg >> 16) & 0xFF) / 255.0
|
||||
let green = Double((bg >> 8) & 0xFF) / 255.0
|
||||
|
|
|
|||
|
|
@ -383,7 +383,8 @@ struct TerminalHostingView: UIViewRepresentable {
|
|||
from oldSnapshot: BufferSnapshot,
|
||||
to newSnapshot: BufferSnapshot
|
||||
)
|
||||
-> String {
|
||||
-> String
|
||||
{
|
||||
var output = ""
|
||||
var currentFg: Int?
|
||||
var currentBg: Int?
|
||||
|
|
|
|||
|
|
@ -112,7 +112,9 @@ struct TerminalView: View {
|
|||
}
|
||||
)
|
||||
.task {
|
||||
for await notification in NotificationCenter.default.notifications(named: UIResponder.keyboardWillShowNotification) {
|
||||
for await notification in NotificationCenter.default
|
||||
.notifications(named: UIResponder.keyboardWillShowNotification)
|
||||
{
|
||||
if let keyboardFrame = notification.userInfo?[UIResponder.keyboardFrameEndUserInfoKey] as? CGRect {
|
||||
withAnimation(Theme.Animation.standard) {
|
||||
keyboardHeight = keyboardFrame.height
|
||||
|
|
@ -723,7 +725,8 @@ class TerminalViewModel {
|
|||
let parts = dimensions.split(separator: "x")
|
||||
if parts.count == 2,
|
||||
let cols = Int(parts[0]),
|
||||
let rows = Int(parts[1]) {
|
||||
let rows = Int(parts[1])
|
||||
{
|
||||
// Update terminal dimensions
|
||||
terminalCols = cols
|
||||
terminalRows = rows
|
||||
|
|
|
|||
|
|
@ -251,7 +251,8 @@ struct XtermWebView: UIViewRepresentable {
|
|||
case "terminalResize":
|
||||
if let dict = message.body as? [String: Any],
|
||||
let cols = dict["cols"] as? Int,
|
||||
let rows = dict["rows"] as? Int {
|
||||
let rows = dict["rows"] as? Int
|
||||
{
|
||||
parent.onResize(cols, rows)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -47,6 +47,9 @@ if command -v xcpretty &> /dev/null; then
|
|||
-scheme VibeTunnel-iOS \
|
||||
-destination "platform=iOS Simulator,id=$SIMULATOR_ID" \
|
||||
-resultBundlePath TestResults.xcresult \
|
||||
CODE_SIGN_IDENTITY="" \
|
||||
CODE_SIGNING_REQUIRED=NO \
|
||||
CODE_SIGNING_ALLOWED=NO \
|
||||
2>&1 | xcpretty || {
|
||||
EXIT_CODE=$?
|
||||
echo "Tests failed with exit code: $EXIT_CODE"
|
||||
|
|
@ -68,6 +71,9 @@ else
|
|||
-scheme VibeTunnel-iOS \
|
||||
-destination "platform=iOS Simulator,id=$SIMULATOR_ID" \
|
||||
-resultBundlePath TestResults.xcresult \
|
||||
CODE_SIGN_IDENTITY="" \
|
||||
CODE_SIGNING_REQUIRED=NO \
|
||||
CODE_SIGNING_ALLOWED=NO \
|
||||
|| {
|
||||
EXIT_CODE=$?
|
||||
echo "Tests failed with exit code: $EXIT_CODE"
|
||||
|
|
|
|||
|
|
@ -56,7 +56,8 @@ final class DockIconManager: NSObject, @unchecked Sendable {
|
|||
|
||||
// Log window details for debugging
|
||||
// for window in visibleWindows {
|
||||
// logger.debug(" Visible window: \(window.title.isEmpty ? "(untitled)" : window.title, privacy: .public)")
|
||||
// logger.debug(" Visible window: \(window.title.isEmpty ? "(untitled)" : window.title, privacy:
|
||||
// .public)")
|
||||
// }
|
||||
|
||||
// Show dock if user wants it shown OR if any windows are open
|
||||
|
|
|
|||
|
|
@ -171,7 +171,7 @@ struct GlowingAppIcon: View {
|
|||
enableInteraction: true,
|
||||
glowIntensity: 0.3
|
||||
) {
|
||||
print("Icon clicked!")
|
||||
// Icon clicked - action handled here
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
|
|
|
|||
Loading…
Reference in a new issue