mirror of
https://github.com/samsonjs/vibetunnel.git
synced 2026-03-31 10:25:57 +00:00
Major improvements to the iOS VibeTunnel app: SwiftTerm Integration: - Properly integrated SwiftTerm 1.2.5 for terminal emulation - Fixed naming conflicts and API compatibility issues - Added proper actor isolation with @preconcurrency - Terminal data now feeds correctly via SSE streams Session Management: - Fixed session model to match Go server's JSON response format - Added CodingKeys mapping for field name differences (cmdline, cwd, etc.) - Support for "starting" session status - Enhanced date parsing for both ISO8601 and RFC3339 formats Working Directory Fixes: - Changed default working directory from iOS sandbox to server paths - Now uses ~/ as default (matching web frontend) - Added common server directories as quick options - Server expands ~ to actual home directory Terminal Streaming: - Implemented Asciinema cast v2 format parsing - SSE client properly handles output, resize, and exit events - Added terminal snapshot loading for existing sessions - Fixed special key handling with proper ANSI escape sequences UI Improvements: - Updated session list to grid layout (2 columns) - Added session count and "Kill All" functionality - Redesigned session cards with inline kill/clean buttons - Shows command and working directory in session preview Error Handling: - Added comprehensive logging for debugging - Enhanced error messages for better user experience - Detailed API request/response logging - Network error descriptions (connection refused, timeout, etc.) Server Configuration: - Set default server address to 127.0.0.1:4020 - Sessions now created with spawn_terminal: false for PTY mode - Both web and iOS clients can share terminal sessions The iOS app now provides a full terminal experience using SwiftTerm, mirroring the web frontend's functionality with proper server integration.
4.4 KiB
4.4 KiB
Modern Swift Development
Write idiomatic SwiftUI code following Apple's latest architectural recommendations and best practices.
Core Philosophy
- SwiftUI is the default UI paradigm for Apple platforms - embrace its declarative nature
- Avoid legacy UIKit patterns and unnecessary abstractions
- Focus on simplicity, clarity, and native data flow
- Let SwiftUI handle the complexity - don't fight the framework
Architecture Guidelines
1. Embrace Native State Management
Use SwiftUI's built-in property wrappers appropriately:
@State- Local, ephemeral view state@Binding- Two-way data flow between views@Observable- Shared state (iOS 17+)@ObservableObject- Legacy shared state (pre-iOS 17)@Environment- Dependency injection for app-wide concerns
2. State Ownership Principles
- Views own their local state unless sharing is required
- State flows down, actions flow up
- Keep state as close to where it's used as possible
- Extract shared state only when multiple views need it
3. Modern Async Patterns
- Use
async/awaitas the default for asynchronous operations - Leverage
.taskmodifier for lifecycle-aware async work - Avoid Combine unless absolutely necessary
- Handle errors gracefully with try/catch
4. View Composition
- Build UI with small, focused views
- Extract reusable components naturally
- Use view modifiers to encapsulate common styling
- Prefer composition over inheritance
5. Code Organization
- Organize by feature, not by type (avoid Views/, Models/, ViewModels/ folders)
- Keep related code together in the same file when appropriate
- Use extensions to organize large files
- Follow Swift naming conventions consistently
Implementation Patterns
Simple State Example
struct CounterView: View {
@State private var count = 0
var body: some View {
VStack {
Text("Count: \(count)")
Button("Increment") {
count += 1
}
}
}
}
Shared State with @Observable
@Observable
class UserSession {
var isAuthenticated = false
var currentUser: User?
func signIn(user: User) {
currentUser = user
isAuthenticated = true
}
}
struct MyApp: App {
@State private var session = UserSession()
var body: some Scene {
WindowGroup {
ContentView()
.environment(session)
}
}
}
Async Data Loading
struct ProfileView: View {
@State private var profile: Profile?
@State private var isLoading = false
@State private var error: Error?
var body: some View {
Group {
if isLoading {
ProgressView()
} else if let profile {
ProfileContent(profile: profile)
} else if let error {
ErrorView(error: error)
}
}
.task {
await loadProfile()
}
}
private func loadProfile() async {
isLoading = true
defer { isLoading = false }
do {
profile = try await ProfileService.fetch()
} catch {
self.error = error
}
}
}
Best Practices
DO:
- Write self-contained views when possible
- Use property wrappers as intended by Apple
- Test logic in isolation, preview UI visually
- Handle loading and error states explicitly
- Keep views focused on presentation
- Use Swift's type system for safety
DON'T:
- Create ViewModels for every view
- Move state out of views unnecessarily
- Add abstraction layers without clear benefit
- Use Combine for simple async operations
- Fight SwiftUI's update mechanism
- Overcomplicate simple features
Testing Strategy
- Unit test business logic and data transformations
- Use SwiftUI Previews for visual testing
- Test @Observable classes independently
- Keep tests simple and focused
- Don't sacrifice code clarity for testability
Modern Swift Features
- Use Swift Concurrency (async/await, actors)
- Leverage Swift 6 data race safety when available
- Utilize property wrappers effectively
- Embrace value types where appropriate
- Use protocols for abstraction, not just for testing
Summary
Write SwiftUI code that looks and feels like SwiftUI. The framework has matured significantly - trust its patterns and tools. Focus on solving user problems rather than implementing architectural patterns from other platforms.