From 1472b0dee547cb9de241fd63d7b002ad1e57d400 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Mon, 30 Jun 2025 01:00:12 +0100 Subject: [PATCH] feat(ios): add light mode support, iPad keyboard shortcuts, and extended toolbar (#141) --- ios/VibeTunnel/App/VibeTunnelApp.swift | 10 + ios/VibeTunnel/Models/TerminalData.swift | 38 +++ .../Views/Connection/ConnectionView.swift | 1 - .../Connection/EnhancedConnectionView.swift | 1 - .../Views/FileBrowser/FilePreviewView.swift | 2 - ios/VibeTunnel/Views/FileBrowserView.swift | 3 +- ios/VibeTunnel/Views/FileEditorView.swift | 1 - .../Views/Sessions/SessionCreateView.swift | 1 - .../Views/Settings/SettingsView.swift | 47 +++- ios/VibeTunnel/Views/SystemLogsView.swift | 1 - .../Views/Terminal/CastPlayerView.swift | 1 - .../Views/Terminal/CtrlKeyGrid.swift | 1 - .../Views/Terminal/FontSizeSheet.swift | 1 - .../Views/Terminal/FullscreenTextInput.swift | 1 - .../Views/Terminal/TerminalThemeSheet.swift | 1 - .../Views/Terminal/TerminalToolbar.swift | 73 ++++- .../Views/Terminal/TerminalView.swift | 92 ++++++- .../Views/Terminal/TerminalWidthSheet.swift | 1 - .../Views/Terminal/WidthSelectorPopover.swift | 2 - .../Views/Welcome/WelcomeView.swift | 2 +- ios/conversion.md | 258 ++++++++++++++++++ 21 files changed, 517 insertions(+), 21 deletions(-) create mode 100644 ios/conversion.md diff --git a/ios/VibeTunnel/App/VibeTunnelApp.swift b/ios/VibeTunnel/App/VibeTunnelApp.swift index 8827a36c..e876ec80 100644 --- a/ios/VibeTunnel/App/VibeTunnelApp.swift +++ b/ios/VibeTunnel/App/VibeTunnelApp.swift @@ -7,6 +7,7 @@ struct VibeTunnelApp: App { @State private var connectionManager = ConnectionManager() @State private var navigationManager = NavigationManager() @State private var networkMonitor = NetworkMonitor.shared + @AppStorage("colorSchemePreference") private var colorSchemePreferenceRaw = "system" init() { // Configure app logging level @@ -26,11 +27,20 @@ struct VibeTunnelApp: App { // Initialize network monitoring _ = networkMonitor } + .preferredColorScheme(colorScheme) #if targetEnvironment(macCatalyst) .macCatalystWindowStyle(getStoredWindowStyle()) #endif } } + + private var colorScheme: ColorScheme? { + switch colorSchemePreferenceRaw { + case "light": return .light + case "dark": return .dark + default: return nil // System default + } + } #if targetEnvironment(macCatalyst) private func getStoredWindowStyle() -> MacWindowStyle { diff --git a/ios/VibeTunnel/Models/TerminalData.swift b/ios/VibeTunnel/Models/TerminalData.swift index 4690383c..caf5f8d7 100644 --- a/ios/VibeTunnel/Models/TerminalData.swift +++ b/ios/VibeTunnel/Models/TerminalData.swift @@ -130,6 +130,44 @@ struct TerminalInput: Codable { /// Control-E (move to end of line). case ctrlE = "\u{0005}" + // MARK: - Function Keys + + /// F1 key. + case f1 = "\u{001B}OP" + /// F2 key. + case f2 = "\u{001B}OQ" + /// F3 key. + case f3 = "\u{001B}OR" + /// F4 key. + case f4 = "\u{001B}OS" + /// F5 key. + case f5 = "\u{001B}[15~" + /// F6 key. + case f6 = "\u{001B}[17~" + /// F7 key. + case f7 = "\u{001B}[18~" + /// F8 key. + case f8 = "\u{001B}[19~" + /// F9 key. + case f9 = "\u{001B}[20~" + /// F10 key. + case f10 = "\u{001B}[21~" + /// F11 key. + case f11 = "\u{001B}[23~" + /// F12 key. + case f12 = "\u{001B}[24~" + + // MARK: - Additional Special Characters + + /// Backslash character. + case backslash = "\\" + /// Pipe character. + case pipe = "|" + /// Backtick character. + case backtick = "`" + /// Tilde character. + case tilde = "~" + // MARK: - Web Compatibility /// Control-Enter combination (web frontend compatibility). diff --git a/ios/VibeTunnel/Views/Connection/ConnectionView.swift b/ios/VibeTunnel/Views/Connection/ConnectionView.swift index ef3615fa..c3778c0c 100644 --- a/ios/VibeTunnel/Views/Connection/ConnectionView.swift +++ b/ios/VibeTunnel/Views/Connection/ConnectionView.swift @@ -88,7 +88,6 @@ struct ConnectionView: View { } } .navigationViewStyle(StackNavigationViewStyle()) - .preferredColorScheme(.dark) .onAppear { viewModel.loadLastConnection() } diff --git a/ios/VibeTunnel/Views/Connection/EnhancedConnectionView.swift b/ios/VibeTunnel/Views/Connection/EnhancedConnectionView.swift index fd6dc6c7..0b751edb 100644 --- a/ios/VibeTunnel/Views/Connection/EnhancedConnectionView.swift +++ b/ios/VibeTunnel/Views/Connection/EnhancedConnectionView.swift @@ -81,7 +81,6 @@ struct EnhancedConnectionView: View { } } .navigationViewStyle(StackNavigationViewStyle()) - .preferredColorScheme(.dark) .onAppear { profilesViewModel.loadProfiles() } diff --git a/ios/VibeTunnel/Views/FileBrowser/FilePreviewView.swift b/ios/VibeTunnel/Views/FileBrowser/FilePreviewView.swift index 14ed5229..8920017f 100644 --- a/ios/VibeTunnel/Views/FileBrowser/FilePreviewView.swift +++ b/ios/VibeTunnel/Views/FileBrowser/FilePreviewView.swift @@ -58,7 +58,6 @@ struct FilePreviewView: View { } } } - .preferredColorScheme(.dark) .task { await loadPreview() } @@ -240,7 +239,6 @@ struct GitDiffView: View { } } } - .preferredColorScheme(.dark) } } diff --git a/ios/VibeTunnel/Views/FileBrowserView.swift b/ios/VibeTunnel/Views/FileBrowserView.swift index 503f0ce8..951626fd 100644 --- a/ios/VibeTunnel/Views/FileBrowserView.swift +++ b/ios/VibeTunnel/Views/FileBrowserView.swift @@ -389,7 +389,7 @@ struct FileBrowserView: View { .overlay { if quickLookManager.isDownloading { ZStack { - Color.black.opacity(0.8) + Theme.Colors.overlayBackground .ignoresSafeArea() VStack(spacing: 20) { @@ -414,7 +414,6 @@ struct FileBrowserView: View { } } } - .preferredColorScheme(.dark) .onAppear { viewModel.loadDirectory(path: initialPath) } diff --git a/ios/VibeTunnel/Views/FileEditorView.swift b/ios/VibeTunnel/Views/FileEditorView.swift index c5b46f47..de256c8f 100644 --- a/ios/VibeTunnel/Views/FileEditorView.swift +++ b/ios/VibeTunnel/Views/FileEditorView.swift @@ -109,7 +109,6 @@ struct FileEditorView: View { Text(error) } } - .preferredColorScheme(.dark) .onAppear { isTextEditorFocused = true } diff --git a/ios/VibeTunnel/Views/Sessions/SessionCreateView.swift b/ios/VibeTunnel/Views/Sessions/SessionCreateView.swift index eb379768..a95e15ba 100644 --- a/ios/VibeTunnel/Views/Sessions/SessionCreateView.swift +++ b/ios/VibeTunnel/Views/Sessions/SessionCreateView.swift @@ -307,7 +307,6 @@ struct SessionCreateView: View { focusedField = .command } } - .preferredColorScheme(.dark) .sheet(isPresented: $showFileBrowser) { FileBrowserView(initialPath: workingDirectory) { selectedPath in workingDirectory = selectedPath diff --git a/ios/VibeTunnel/Views/Settings/SettingsView.swift b/ios/VibeTunnel/Views/Settings/SettingsView.swift index 17474e62..d5cf3531 100644 --- a/ios/VibeTunnel/Views/Settings/SettingsView.swift +++ b/ios/VibeTunnel/Views/Settings/SettingsView.swift @@ -81,7 +81,6 @@ struct SettingsView: View { } } } - .preferredColorScheme(.dark) } } @@ -97,9 +96,55 @@ struct GeneralSettingsView: View { private var enableURLDetection = true @AppStorage("enableLivePreviews") private var enableLivePreviews = true + @AppStorage("colorSchemePreference") + private var colorSchemePreferenceRaw = "system" + + enum ColorSchemePreference: String, CaseIterable { + case system = "system" + case light = "light" + case dark = "dark" + + var displayName: String { + switch self { + case .system: return "System" + case .light: return "Light" + case .dark: return "Dark" + } + } + } + + private var colorSchemePreference: ColorSchemePreference { + ColorSchemePreference(rawValue: colorSchemePreferenceRaw) ?? .system + } var body: some View { VStack(alignment: .leading, spacing: Theme.Spacing.large) { + // Appearance Section + VStack(alignment: .leading, spacing: Theme.Spacing.medium) { + Text("Appearance") + .font(.headline) + .foregroundColor(Theme.Colors.terminalForeground) + + VStack(spacing: Theme.Spacing.medium) { + // Color Scheme + VStack(alignment: .leading, spacing: Theme.Spacing.small) { + Text("Color Scheme") + .font(Theme.Typography.terminalSystem(size: 14)) + .foregroundColor(Theme.Colors.terminalForeground.opacity(0.7)) + + Picker("Color Scheme", selection: $colorSchemePreferenceRaw) { + ForEach(ColorSchemePreference.allCases, id: \.self) { preference in + Text(preference.displayName).tag(preference.rawValue) + } + } + .pickerStyle(SegmentedPickerStyle()) + } + .padding() + .background(Theme.Colors.cardBackground) + .cornerRadius(Theme.CornerRadius.card) + } + } + // Terminal Defaults Section VStack(alignment: .leading, spacing: Theme.Spacing.medium) { Text("Terminal Defaults") diff --git a/ios/VibeTunnel/Views/SystemLogsView.swift b/ios/VibeTunnel/Views/SystemLogsView.swift index c43243b3..b02ab20e 100644 --- a/ios/VibeTunnel/Views/SystemLogsView.swift +++ b/ios/VibeTunnel/Views/SystemLogsView.swift @@ -149,7 +149,6 @@ struct SystemLogsView: View { } } } - .preferredColorScheme(.dark) .task { await loadLogs() startAutoRefresh() diff --git a/ios/VibeTunnel/Views/Terminal/CastPlayerView.swift b/ios/VibeTunnel/Views/Terminal/CastPlayerView.swift index c44b0225..96c1a264 100644 --- a/ios/VibeTunnel/Views/Terminal/CastPlayerView.swift +++ b/ios/VibeTunnel/Views/Terminal/CastPlayerView.swift @@ -45,7 +45,6 @@ struct CastPlayerView: View { } } .navigationViewStyle(StackNavigationViewStyle()) - .preferredColorScheme(.dark) .onAppear { viewModel.loadCastFile(from: castFileURL) } diff --git a/ios/VibeTunnel/Views/Terminal/CtrlKeyGrid.swift b/ios/VibeTunnel/Views/Terminal/CtrlKeyGrid.swift index 90d8782e..ad19cfae 100644 --- a/ios/VibeTunnel/Views/Terminal/CtrlKeyGrid.swift +++ b/ios/VibeTunnel/Views/Terminal/CtrlKeyGrid.swift @@ -101,7 +101,6 @@ struct CtrlKeyGrid: View { } } } - .preferredColorScheme(.dark) } private var currentKeys: [(String, String)] { diff --git a/ios/VibeTunnel/Views/Terminal/FontSizeSheet.swift b/ios/VibeTunnel/Views/Terminal/FontSizeSheet.swift index ff7b96d3..142e2734 100644 --- a/ios/VibeTunnel/Views/Terminal/FontSizeSheet.swift +++ b/ios/VibeTunnel/Views/Terminal/FontSizeSheet.swift @@ -121,6 +121,5 @@ struct FontSizeSheet: View { } } } - .preferredColorScheme(.dark) } } diff --git a/ios/VibeTunnel/Views/Terminal/FullscreenTextInput.swift b/ios/VibeTunnel/Views/Terminal/FullscreenTextInput.swift index b00882e2..a84dd021 100644 --- a/ios/VibeTunnel/Views/Terminal/FullscreenTextInput.swift +++ b/ios/VibeTunnel/Views/Terminal/FullscreenTextInput.swift @@ -172,7 +172,6 @@ struct FullscreenTextInput: View { } } } - .preferredColorScheme(.dark) .onAppear { isFocused = true } diff --git a/ios/VibeTunnel/Views/Terminal/TerminalThemeSheet.swift b/ios/VibeTunnel/Views/Terminal/TerminalThemeSheet.swift index bcf04aa2..cd3b26f3 100644 --- a/ios/VibeTunnel/Views/Terminal/TerminalThemeSheet.swift +++ b/ios/VibeTunnel/Views/Terminal/TerminalThemeSheet.swift @@ -108,7 +108,6 @@ struct TerminalThemeSheet: View { } } } - .preferredColorScheme(.dark) } } diff --git a/ios/VibeTunnel/Views/Terminal/TerminalToolbar.swift b/ios/VibeTunnel/Views/Terminal/TerminalToolbar.swift index 4fec4232..a9234b57 100644 --- a/ios/VibeTunnel/Views/Terminal/TerminalToolbar.swift +++ b/ios/VibeTunnel/Views/Terminal/TerminalToolbar.swift @@ -145,7 +145,78 @@ struct TerminalToolbar: View { } } - // Third row - custom Ctrl key input + // Third row - F-keys (F1-F6) + HStack(spacing: Theme.Spacing.extraSmall) { + ForEach(["F1", "F2", "F3", "F4", "F5", "F6"], id: \.self) { fkey in + ToolbarButton(label: fkey, width: 44) { + HapticFeedback.impact(.light) + switch fkey { + case "F1": onSpecialKey(.f1) + case "F2": onSpecialKey(.f2) + case "F3": onSpecialKey(.f3) + case "F4": onSpecialKey(.f4) + case "F5": onSpecialKey(.f5) + case "F6": onSpecialKey(.f6) + default: break + } + } + } + + Spacer() + } + + // Fourth row - F-keys (F7-F12) + HStack(spacing: Theme.Spacing.extraSmall) { + ForEach(["F7", "F8", "F9", "F10", "F11", "F12"], id: \.self) { fkey in + ToolbarButton(label: fkey, width: 44) { + HapticFeedback.impact(.light) + switch fkey { + case "F7": onSpecialKey(.f7) + case "F8": onSpecialKey(.f8) + case "F9": onSpecialKey(.f9) + case "F10": onSpecialKey(.f10) + case "F11": onSpecialKey(.f11) + case "F12": onSpecialKey(.f12) + default: break + } + } + } + + Spacer() + } + + // Fifth row - Special characters + HStack(spacing: Theme.Spacing.extraSmall) { + ToolbarButton(label: "\\") { + HapticFeedback.impact(.light) + onSpecialKey(.backslash) + } + + ToolbarButton(label: "|") { + HapticFeedback.impact(.light) + onSpecialKey(.pipe) + } + + ToolbarButton(label: "`") { + HapticFeedback.impact(.light) + onSpecialKey(.backtick) + } + + ToolbarButton(label: "~") { + HapticFeedback.impact(.light) + onSpecialKey(.tilde) + } + + ToolbarButton(label: "END") { + HapticFeedback.impact(.light) + // Send Ctrl+E for end + onSpecialKey(.ctrlE) + } + + Spacer() + } + + // Sixth row - custom Ctrl key input HStack(spacing: Theme.Spacing.extraSmall) { Text("CTRL +") .font(Theme.Typography.terminalSystem(size: 12)) diff --git a/ios/VibeTunnel/Views/Terminal/TerminalView.swift b/ios/VibeTunnel/Views/Terminal/TerminalView.swift index 2a3c9f36..bd718d66 100644 --- a/ios/VibeTunnel/Views/Terminal/TerminalView.swift +++ b/ios/VibeTunnel/Views/Terminal/TerminalView.swift @@ -51,7 +51,6 @@ struct TerminalView: View { recordingIndicator } } - .preferredColorScheme(.dark) .focusable() .onAppear { viewModel.connect() @@ -149,6 +148,7 @@ struct TerminalView: View { showScrollToBottom = !newValue } } + // iPad keyboard shortcuts .onKeyPress(keys: ["o"]) { press in if press.modifiers.contains(.command) && session.isRunning { showingFileBrowser = true @@ -156,6 +156,96 @@ struct TerminalView: View { } return .ignored } + .onKeyPress(keys: ["+"]) { press in + if press.modifiers.contains(.command) { + // Increase font size + withAnimation(Theme.Animation.quick) { + fontSize = min(fontSize + 2, 30) + } + return .handled + } + return .ignored + } + .onKeyPress(keys: ["-"]) { press in + if press.modifiers.contains(.command) { + // Decrease font size + withAnimation(Theme.Animation.quick) { + fontSize = max(fontSize - 2, 8) + } + return .handled + } + return .ignored + } + .onKeyPress(keys: ["t"]) { press in + if press.modifiers.contains(.command) { + // Toggle theme + let themes = TerminalTheme.allThemes + if let currentIndex = themes.firstIndex(where: { $0.id == selectedTheme.id }) { + let nextIndex = (currentIndex + 1) % themes.count + selectedTheme = themes[nextIndex] + TerminalTheme.selected = selectedTheme + } + return .handled + } + return .ignored + } + .onKeyPress(keys: ["k"]) { press in + if press.modifiers.contains(.command) { + // Clear terminal + viewModel.sendSpecialKey(.ctrlL) + return .handled + } + return .ignored + } + .onKeyPress(keys: ["c"]) { press in + if press.modifiers.contains(.command) && !press.modifiers.contains(.shift) { + // Copy to clipboard + if let content = viewModel.getBufferContent() { + UIPasteboard.general.string = content + HapticFeedback.notification(.success) + } + return .handled + } + return .ignored + } + .onKeyPress(keys: ["r"]) { press in + if press.modifiers.contains(.command) { + // Start/stop recording + if viewModel.castRecorder.isRecording { + viewModel.stopRecording() + } else { + viewModel.startRecording() + } + return .handled + } + return .ignored + } + .onKeyPress(keys: ["w"]) { press in + if press.modifiers.contains(.command) { + // Change terminal width + showingTerminalWidthSheet = true + return .handled + } + return .ignored + } + .onKeyPress(keys: ["d"]) { press in + if press.modifiers.contains(.command) { + // Toggle debug menu + showingDebugMenu.toggle() + return .handled + } + return .ignored + } + .onKeyPress(keys: [.escape]) { _ in + // Send escape key to terminal + viewModel.sendSpecialKey(.escape) + return .handled + } + .onKeyPress(keys: [.tab]) { _ in + // Send tab key to terminal + viewModel.sendSpecialKey(.tab) + return .handled + } .sheet(isPresented: $showingExportSheet) { if let url = exportedFileURL { ShareSheet(items: [url]) diff --git a/ios/VibeTunnel/Views/Terminal/TerminalWidthSheet.swift b/ios/VibeTunnel/Views/Terminal/TerminalWidthSheet.swift index 441b9326..c20d170c 100644 --- a/ios/VibeTunnel/Views/Terminal/TerminalWidthSheet.swift +++ b/ios/VibeTunnel/Views/Terminal/TerminalWidthSheet.swift @@ -274,7 +274,6 @@ struct TerminalWidthSheet: View { } } } - .preferredColorScheme(.dark) } private func applyCustomWidth() { diff --git a/ios/VibeTunnel/Views/Terminal/WidthSelectorPopover.swift b/ios/VibeTunnel/Views/Terminal/WidthSelectorPopover.swift index 96770091..ce943c6b 100644 --- a/ios/VibeTunnel/Views/Terminal/WidthSelectorPopover.swift +++ b/ios/VibeTunnel/Views/Terminal/WidthSelectorPopover.swift @@ -72,7 +72,6 @@ struct WidthSelectorPopover: View { } } } - .preferredColorScheme(.dark) .frame(width: 320, height: 400) .sheet(isPresented: $showCustomInput) { CustomWidthSheet( @@ -188,7 +187,6 @@ private struct CustomWidthSheet: View { } } } - .preferredColorScheme(.dark) .onAppear { isFocused = true } diff --git a/ios/VibeTunnel/Views/Welcome/WelcomeView.swift b/ios/VibeTunnel/Views/Welcome/WelcomeView.swift index c7637328..e5178af3 100644 --- a/ios/VibeTunnel/Views/Welcome/WelcomeView.swift +++ b/ios/VibeTunnel/Views/Welcome/WelcomeView.swift @@ -46,7 +46,7 @@ struct WelcomeView: View { HStack(spacing: 8) { ForEach(0..<5) { index in Circle() - .fill(index == currentPage ? Theme.Colors.primaryAccent : Color.gray.opacity(0.3)) + .fill(index == currentPage ? Theme.Colors.primaryAccent : Theme.Colors.secondaryText.opacity(0.3)) .frame(width: 8, height: 8) .animation(.easeInOut, value: currentPage) } diff --git a/ios/conversion.md b/ios/conversion.md new file mode 100644 index 00000000..66344723 --- /dev/null +++ b/ios/conversion.md @@ -0,0 +1,258 @@ +# VibeTunnel iOS Feature Parity Conversion Guide + +This document provides a comprehensive comparison between the web frontend and iOS app features, with recommendations for achieving feature parity while maintaining a native iOS experience. + +## Executive Summary + +The iOS app already implements most core functionality but lacks several features present in the web frontend. Key missing features include: full theme support (light mode), SSH key management UI, advanced keyboard shortcuts, notification support, and some terminal features. Most missing features can be adapted to iOS with appropriate native patterns. + +## Feature Comparison Table + +### ✅ Core Terminal Features + +| Feature | Web | iOS | Status | iOS Adaptation Notes | +|---------|-----|-----|--------|---------------------| +| Terminal emulation | xterm.js | SwiftTerm + xterm.js | ✅ Complete | Dual renderer approach is excellent | +| Copy/paste | Native clipboard | Touch selection | ✅ Complete | iOS implementation is more intuitive | +| URL highlighting | Clickable URLs | URL detection (configurable) | ✅ Complete | Native iOS text detection | +| Font size control | 8-32px range | 8-32pt with presets | ✅ Complete | Quick preset buttons are better for mobile | +| Terminal width presets | 80/100/120/132/160/unlimited | 80/100/120/160/unlimited | ✅ Complete | Good selection for mobile | +| Fit horizontally | ✓ | ✓ | ✅ Complete | - | +| Cursor following | Auto-scroll | Auto-scroll (configurable) | ✅ Complete | Toggle in settings is good | +| ANSI color support | Full 256 + true color | Full support | ✅ Complete | - | +| Unicode support | Full | Full | ✅ Complete | - | + +### 🔧 Session Management + +| Feature | Web | iOS | Status | iOS Adaptation Notes | +|---------|-----|-----|--------|---------------------| +| Session list | Grid/list view | List view | ✅ Complete | List view better for mobile | +| Session cards | Status, command, path | All info + live preview | ✅ Enhanced | Live preview is iOS advantage | +| Create sessions | Local + SSH | Local only (UI) | ⚠️ Partial | SSH UI needed | +| Kill sessions | Individual + kill all | Individual + kill all | ✅ Complete | - | +| Hide exited sessions | Toggle | Toggle | ✅ Complete | - | +| Clean exited | Bulk remove | Bulk remove | ✅ Complete | - | +| Auto-refresh | 3 seconds | 3 seconds | ✅ Complete | - | +| Session search | By command/path | By any field | ✅ Enhanced | Better search in iOS | + +### ⌨️ Input & Keyboard Features + +| Feature | Web | iOS | Status | iOS Adaptation Notes | +|---------|-----|-----|--------|---------------------| +| Quick keys bar | F-keys, Ctrl combos, arrows | Special keys toolbar | ✅ Complete | iOS toolbar is more accessible | +| Ctrl+Alpha overlay | Grid overlay | Grid sheet | ✅ Complete | Native sheet presentation | +| Function keys | F1-F12 in quick bar | Missing | ❌ TODO | Add to extended keyboard | +| Arrow key repeat | Hold to repeat | Single tap only | ❌ TODO | Implement long press repeat | +| Special characters | Full set in quick bar | Limited set | ⚠️ Partial | Add more special chars | +| Keyboard shortcuts | Cmd/Ctrl+O, Escape | Missing | ❌ TODO | Add iPad keyboard shortcuts | +| Raw input mode | Direct keyboard | Raw mode toggle | ✅ Complete | - | + +### 🎨 Themes & Appearance + +| Feature | Web | iOS | Status | iOS Adaptation Notes | +|---------|-----|-----|--------|---------------------| +| Dark theme | Full dark theme | Dark only | ✅ Complete | - | +| Light theme | Not implemented | Not implemented | ❌ TODO | **Critical: Add light mode** | +| Theme selection | N/A | 5 themes (dark only) | ✅ Enhanced | More themes than web | +| Custom themes | No | No | 🔮 Future | Consider theme editor | + +### 📁 File Browser + +| Feature | Web | iOS | Status | iOS Adaptation Notes | +|---------|-----|-----|--------|---------------------| +| Directory navigation | Breadcrumb nav | Hierarchical nav | ✅ Complete | iOS navigation is cleaner | +| File preview | Monaco editor | Native viewer | ✅ Complete | Quick Look is better | +| Syntax highlighting | Monaco | Native | ✅ Complete | - | +| Git status badges | ✓ | ✓ | ✅ Complete | - | +| Git diff viewer | Side-by-side | Not implemented | ❌ TODO | Use native diff view | +| Hidden files toggle | ✓ | ✓ | ✅ Complete | - | +| Path copying | ✓ | Name + path options | ✅ Enhanced | More copy options | +| Git filter | All/changed | All/changed | ✅ Complete | - | +| Image preview | Inline | Quick Look | ✅ Complete | Native preview is better | +| File editing | Monaco editor | View only | ❌ TODO | Add basic text editor | +| File operations | Read only | Create dirs only | ❌ TODO | Add rename, delete | + +### 🔐 Authentication & Security + +| Feature | Web | iOS | Status | iOS Adaptation Notes | +|---------|-----|-----|--------|---------------------| +| Password auth | PAM-based | ✓ | ✅ Complete | - | +| SSH key management | Generate/import/manage | Backend only | ❌ TODO | **Critical: Add SSH key UI** | +| Browser SSH agent | In-browser agent | Not applicable | N/A | Use iOS keychain | +| JWT tokens | ✓ | ✓ | ✅ Complete | - | +| No-auth mode | ✓ | ✓ | ✅ Complete | - | +| Biometric auth | No | No | ❌ TODO | Add Face ID/Touch ID | + +### 🔔 Notifications & Feedback + +| Feature | Web | iOS | Status | iOS Adaptation Notes | +|---------|-----|-----|--------|---------------------| +| Toast notifications | Success/error toasts | Alert/banner | ✅ Complete | Native alerts better | +| Push notifications | Browser push | Not implemented | ❌ TODO | **Add push support** | +| Sound/vibration | Settings available | Haptics only | ⚠️ Partial | Add sound options | +| Terminal bell | Visual/audio | Haptic feedback | ✅ Complete | Haptics are perfect | + +### 🛠️ Advanced Features + +| Feature | Web | iOS | Status | iOS Adaptation Notes | +|---------|-----|-----|--------|---------------------| +| Split view | Side-by-side list/terminal | iPad multitasking | ✅ Different | iPad split view is better | +| WebSocket binary | ✓ | ✓ | ✅ Complete | - | +| SSE streaming | Text output | Not used | N/A | WebSocket is sufficient | +| Offline support | Service worker | Basic offline handling | ⚠️ Partial | Improve offline mode | +| PWA features | Installable | Native app | N/A | Already native | +| URL routing | Deep links | URL schemes | ✅ Complete | - | +| Hot reload | Dev mode | N/A | N/A | Not needed | +| Terminal search | Not implemented | Not implemented | ❌ TODO | Add find in buffer | +| Export session | Not implemented | Text export only | ⚠️ Partial | Add PDF export | + +### 📱 Mobile-Specific Features + +| Feature | Web | iOS | Status | iOS Adaptation Notes | +|---------|-----|-----|--------|---------------------| +| Swipe gestures | Right for sidebar | Left to dismiss | ✅ Enhanced | More gesture support | +| Pinch to zoom | No | ✓ | ✅ iOS Exclusive | Great for accessibility | +| Haptic feedback | No | Throughout app | ✅ iOS Exclusive | Excellent feedback | +| Safe area handling | CSS env() | Native | ✅ Complete | Better implementation | +| Cast file support | Converter utility | Full support + sharing | ✅ Enhanced | File type registration | +| Recording | No | Asciinema recording | ✅ iOS Exclusive | Unique feature | + +## Priority Implementation Recommendations + +### 🚨 Critical (Implement Immediately) + +1. **Light Mode Support** + - Implement full light/dark mode switching + - Update all colors to use semantic colors + - Test all UI elements in both modes + - Add automatic mode based on system settings + +2. **SSH Key Management UI** + - Create SSH key list view + - Add key generation (RSA, Ed25519) + - Import key functionality + - Key deletion and management + - Integration with iOS Keychain + +3. **iPad Keyboard Shortcuts** + - Cmd+O: Open file browser + - Cmd+K: Clear terminal + - Cmd+F: Find in buffer + - Cmd+N: New session + - Escape: Exit session + +### ⚠️ High Priority + +4. **Advanced Keyboard Features** + - Function keys (F1-F12) in extended toolbar + - Arrow key repeat on long press + - More special characters (|, \, ~, `, {, }, [, ]) + - Customizable quick keys + +5. **Push Notifications** + - Session completion notifications + - Error notifications + - Background session monitoring + - Notification settings UI + +6. **File Editor** + - Basic text editor using TextEditor + - Syntax highlighting + - Save functionality + - Integration with terminal (open in editor) + +### 🔔 Medium Priority + +7. **Terminal Search** + - Find in buffer functionality + - Search highlighting + - Next/previous navigation + - Case sensitive toggle + +8. **Git Diff Viewer** + - Native diff view component + - Side-by-side comparison + - Syntax highlighting in diffs + - Integration with file browser + +9. **Enhanced File Operations** + - File/folder rename + - File deletion (with confirmation) + - File permissions viewer + - Bulk operations + +10. **Session Export** + - Export as PDF with formatting + - Export with ANSI colors preserved + - Share sheet integration + +### 💡 Nice to Have + +11. **Biometric Authentication** + - Face ID/Touch ID for app access + - Biometric protection for SSH keys + - Quick unlock option + +12. **Advanced Terminal Features** + - Terminal multiplexing (split panes) + - Session templates + - Command aliases + - Snippet management + +13. **Collaboration Features** + - Session sharing via URL + - Read-only session viewing + - Collaborative editing + +## iOS-Specific Design Considerations + +### Native Patterns to Embrace + +1. **Navigation**: Use standard iOS navigation patterns instead of web-style routing +2. **Sheets & Popovers**: Replace overlays with native sheets and popovers +3. **Gestures**: Leverage iOS gesture recognizers for intuitive interactions +4. **Haptics**: Continue extensive use of haptic feedback +5. **System Integration**: Deeper integration with iOS features (Shortcuts, Widgets) + +### UI Adaptations + +1. **Compact Layouts**: Optimize for smaller iPhone screens +2. **Dynamic Type**: Support for accessibility text sizes +3. **iPad Features**: Leverage larger screen with split views, multiple windows +4. **Context Menus**: Add long-press context menus throughout +5. **Pull to Refresh**: Already implemented, maintain consistency + +### Performance Considerations + +1. **Background Execution**: Handle background session monitoring efficiently +2. **Memory Management**: Optimize terminal buffer memory usage +3. **Battery Life**: Minimize background network activity +4. **Smooth Scrolling**: Maintain 60fps scrolling in terminal output + +## Implementation Timeline + +### Phase 1 (Week 1-2) +- Light mode support +- SSH key management UI +- iPad keyboard shortcuts + +### Phase 2 (Week 3-4) +- Advanced keyboard features +- Push notifications +- Basic file editor + +### Phase 3 (Week 5-6) +- Terminal search +- Git diff viewer +- File operations + +### Phase 4 (Week 7-8) +- Session export improvements +- Biometric authentication +- Polish and testing + +## Conclusion + +The iOS app has a strong foundation with excellent mobile-specific features like haptics, recording, and native UI patterns. The main gaps are in theme support, SSH key management, and some advanced terminal features. By implementing the recommended features while maintaining iOS design principles, VibeTunnel can achieve feature parity while providing a superior mobile experience. + +The focus should be on making the app feel completely native while matching the web's functionality. Features like biometric authentication, widgets, and Shortcuts integration could make the iOS app exceed the web version in mobile-specific scenarios. \ No newline at end of file