This commit is contained in:
Peter Steinberger 2025-06-23 18:36:36 +02:00
parent a14d02e20f
commit 8768bb0eb3
24 changed files with 149 additions and 148 deletions

View file

@ -89,7 +89,7 @@ final class LivePreviewManager {
} }
} }
case .exit(_): case .exit:
subscription.isSessionActive = false subscription.isSessionActive = false
default: default:

View file

@ -116,11 +116,10 @@ struct SessionCreateView: View {
// Error Message // Error Message
if presentedError != nil { if presentedError != nil {
ErrorBanner( ErrorBanner(
message: presentedError?.error.localizedDescription ?? "An error occurred", message: presentedError?.error.localizedDescription ?? "An error occurred"
onDismiss: { ) {
presentedError = nil presentedError = nil
} }
)
.overlay( .overlay(
RoundedRectangle(cornerRadius: Theme.CornerRadius.small) RoundedRectangle(cornerRadius: Theme.CornerRadius.small)
.stroke(Theme.Colors.errorAccent.opacity(0.3), lineWidth: 1) .stroke(Theme.Colors.errorAccent.opacity(0.3), lineWidth: 1)

View file

@ -388,7 +388,6 @@ struct SessionListView: View {
} }
} }
/// View model for managing session list state and operations. /// View model for managing session list state and operations.
@MainActor @MainActor
@Observable @Observable
@ -463,8 +462,8 @@ struct SessionHeaderView: View {
let onKillAll: () -> Void let onKillAll: () -> Void
let onCleanupAll: () -> Void let onCleanupAll: () -> Void
private var runningCount: Int { sessions.count { $0.isRunning }} private var runningCount: Int { sessions.count { $0.isRunning } }
private var exitedCount: Int { sessions.count { !$0.isRunning }} private var exitedCount: Int { sessions.count { !$0.isRunning } }
var body: some View { var body: some View {
VStack(spacing: Theme.Spacing.medium) { VStack(spacing: Theme.Spacing.medium) {

View file

@ -69,9 +69,8 @@ struct CtrlKeyGrid: View {
ForEach(currentKeys, id: \.0) { key, description in ForEach(currentKeys, id: \.0) { key, description in
CtrlGridKeyButton( CtrlGridKeyButton(
key: key, key: key,
description: description, description: description
onPress: { sendCtrlKey(key) } ) { sendCtrlKey(key) }
)
} }
} }
.padding() .padding()
@ -144,7 +143,7 @@ struct CtrlGridKeyButton: View {
@State private var showingTooltip = false @State private var showingTooltip = false
var body: some View { var body: some View {
Button(action: onPress, label: { Button(action: onPress) {
VStack(spacing: 4) { VStack(spacing: 4) {
Text("^" + key) Text("^" + key)
.font(Theme.Typography.terminalSystem(size: 20, weight: .bold)) .font(Theme.Typography.terminalSystem(size: 20, weight: .bold))
@ -171,7 +170,7 @@ struct CtrlGridKeyButton: View {
color: isPressed ? Theme.Colors.primaryAccent.opacity(0.3) : .clear, color: isPressed ? Theme.Colors.primaryAccent.opacity(0.3) : .clear,
radius: isPressed ? 8 : 0 radius: isPressed ? 8 : 0
) )
}) }
.buttonStyle(PlainButtonStyle()) .buttonStyle(PlainButtonStyle())
.scaleEffect(isPressed ? 0.95 : 1.0) .scaleEffect(isPressed ? 0.95 : 1.0)
.animation(.easeInOut(duration: 0.1), value: isPressed) .animation(.easeInOut(duration: 0.1), value: isPressed)

View file

@ -14,7 +14,7 @@ struct TerminalBufferPreview: View {
} }
var body: some View { var body: some View {
GeometryReader { geometry in GeometryReader { _ in
ScrollViewReader { scrollProxy in ScrollViewReader { scrollProxy in
ScrollView([.horizontal, .vertical], showsIndicators: false) { ScrollView([.horizontal, .vertical], showsIndicators: false) {
VStack(alignment: .leading, spacing: 0) { VStack(alignment: .leading, spacing: 0) {

View file

@ -523,12 +523,11 @@ struct TerminalView: View {
.focused($isInputFocused) .focused($isInputFocused)
.overlay( .overlay(
ScrollToBottomButton( ScrollToBottomButton(
isVisible: showScrollToBottom, isVisible: showScrollToBottom
action: { ) {
viewModel.scrollToBottom() viewModel.scrollToBottom()
showScrollToBottom = false showScrollToBottom = false
} }
)
.padding(.bottom, Theme.Spacing.large) .padding(.bottom, Theme.Spacing.large)
.padding(.leading, Theme.Spacing.large), .padding(.leading, Theme.Spacing.large),
alignment: .bottomLeading alignment: .bottomLeading

View file

@ -230,15 +230,14 @@ final class BunServer {
} }
} catch { } catch {
// Log more detailed error information // Log more detailed error information
let errorMessage: String let errorMessage: String = if let bunError = error as? BunServerError {
if let bunError = error as? BunServerError { bunError.localizedDescription
errorMessage = bunError.localizedDescription
} else if let urlError = error as? URLError { } else if let urlError = error as? URLError {
errorMessage = "Network error: \(urlError.localizedDescription) (Code: \(urlError.code.rawValue))" "Network error: \(urlError.localizedDescription) (Code: \(urlError.code.rawValue))"
} else if let posixError = error as? POSIXError { } else if let posixError = error as? POSIXError {
errorMessage = "System error: \(posixError.localizedDescription) (Code: \(posixError.code.rawValue))" "System error: \(posixError.localizedDescription) (Code: \(posixError.code.rawValue))"
} else { } else {
errorMessage = error.localizedDescription error.localizedDescription
} }
logger.error("Failed to start Bun server: \(errorMessage)") logger.error("Failed to start Bun server: \(errorMessage)")

View file

@ -12,33 +12,33 @@ enum ServerError: LocalizedError {
var errorDescription: String? { var errorDescription: String? {
switch self { switch self {
case .repeatedCrashes: case .repeatedCrashes:
return "Server keeps crashing" "Server keeps crashing"
case .portInUse(let port): case .portInUse(let port):
return "Port \(port) is already in use" "Port \(port) is already in use"
case .startupFailed(let reason): case .startupFailed(let reason):
return "Server startup failed: \(reason)" "Server startup failed: \(reason)"
} }
} }
var failureReason: String? { var failureReason: String? {
switch self { switch self {
case .repeatedCrashes(let count): case .repeatedCrashes(let count):
return "The server crashed \(count) times in a row" "The server crashed \(count) times in a row"
case .portInUse(let port): case .portInUse(let port):
return "Another process is using port \(port)" "Another process is using port \(port)"
case .startupFailed: case .startupFailed:
return nil nil
} }
} }
var recoverySuggestion: String? { var recoverySuggestion: String? {
switch self { switch self {
case .repeatedCrashes: case .repeatedCrashes:
return "Check the logs for errors or try a different port" "Check the logs for errors or try a different port"
case .portInUse: case .portInUse:
return "Stop the other process or choose a different port" "Stop the other process or choose a different port"
case .startupFailed: case .startupFailed:
return "Check the server configuration and try again" "Check the server configuration and try again"
} }
} }
} }
@ -508,28 +508,28 @@ enum ServerManagerError: LocalizedError {
var errorDescription: String? { var errorDescription: String? {
switch self { switch self {
case .portInUseByApp(let appName, let port, _): case .portInUseByApp(let appName, let port, _):
return "Port \(port) is in use by \(appName)" "Port \(port) is in use by \(appName)"
} }
} }
var failureReason: String? { var failureReason: String? {
switch self { switch self {
case .portInUseByApp: case .portInUseByApp:
return "The port is being used by another application" "The port is being used by another application"
} }
} }
var recoverySuggestion: String? { var recoverySuggestion: String? {
switch self { switch self {
case .portInUseByApp(_, _, let alternatives): case .portInUseByApp(_, _, let alternatives):
return "Try one of these ports: \(alternatives.map(String.init).joined(separator: ", "))" "Try one of these ports: \(alternatives.map(String.init).joined(separator: ", "))"
} }
} }
var helpAnchor: String? { var helpAnchor: String? {
switch self { switch self {
case .portInUseByApp: case .portInUseByApp:
return "port-conflict" "port-conflict"
} }
} }
} }

View file

@ -31,7 +31,9 @@ extension View {
_ title: String = "Error", _ title: String = "Error",
error: Binding<Error?>, error: Binding<Error?>,
onDismiss: (() -> Void)? = nil onDismiss: (() -> Void)? = nil
) -> some View { )
-> some View
{
modifier(ErrorAlertModifier(error: error, title: title, onDismiss: onDismiss)) modifier(ErrorAlertModifier(error: error, title: title, onDismiss: onDismiss))
} }
} }
@ -46,7 +48,9 @@ extension Task where Failure == Error {
priority: TaskPriority? = nil, priority: TaskPriority? = nil,
errorBinding: Binding<Error?>, errorBinding: Binding<Error?>,
operation: @escaping () async throws -> T operation: @escaping () async throws -> T
) -> Task<T, Error> { )
-> Task<T, Error>
{
Task<T, Error>(priority: priority) { Task<T, Error>(priority: priority) {
do { do {
return try await operation() return try await operation()
@ -142,7 +146,7 @@ struct AsyncErrorBoundary<Content: View>: View {
// MARK: - Environment Values // MARK: - Environment Values
private struct AsyncErrorHandlerKey: EnvironmentKey { private struct AsyncErrorHandlerKey: EnvironmentKey {
nonisolated(unsafe) static let defaultValue = AsyncErrorHandler(handler: { _ in }) nonisolated(unsafe) static let defaultValue = AsyncErrorHandler { _ in }
} }
extension EnvironmentValues { extension EnvironmentValues {

View file

@ -219,9 +219,9 @@ struct ServerStatusView: View {
private var statusText: String { private var statusText: String {
if isRunning { if isRunning {
return "Server running" "Server running"
} else { } else {
return "Server stopped" "Server stopped"
} }
} }

View file

@ -1,5 +1,5 @@
import SwiftUI
import os import os
import SwiftUI
/// View displaying detailed information about a specific terminal session /// View displaying detailed information about a specific terminal session
struct SessionDetailView: View { struct SessionDetailView: View {

View file

@ -1,5 +1,5 @@
import SwiftUI
import os import os
import SwiftUI
/// Shared glowing app icon component with configurable animation and effects. /// Shared glowing app icon component with configurable animation and effects.
/// ///
@ -7,7 +7,7 @@ import os
/// floating animation, and interactive behaviors. It can be used in both the Welcome /// floating animation, and interactive behaviors. It can be used in both the Welcome
/// and About views with different configurations. /// and About views with different configurations.
struct GlowingAppIcon: View { struct GlowingAppIcon: View {
// Configuration /// Configuration
let size: CGFloat let size: CGFloat
private let logger = Logger(subsystem: "sh.vibetunnel.vibetunnel", category: "GlowingAppIcon") private let logger = Logger(subsystem: "sh.vibetunnel.vibetunnel", category: "GlowingAppIcon")

View file

@ -1,8 +1,8 @@
import AppKit import AppKit
import Foundation import Foundation
import Observation
import os.log import os.log
import SwiftUI import SwiftUI
import Observation
/// Terminal launch result with window/tab information /// Terminal launch result with window/tab information
struct TerminalLaunchResult { struct TerminalLaunchResult {

View file

@ -163,6 +163,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate, @preconcurrency UNUser
} }
// Initialize dock icon visibility through DockIconManager // Initialize dock icon visibility through DockIconManager
DockIconManager.initialize()
DockIconManager.shared.updateDockVisibility() DockIconManager.shared.updateDockVisibility()
// Show welcome screen when version changes // Show welcome screen when version changes
@ -320,7 +321,8 @@ final class AppDelegate: NSObject, NSApplicationDelegate, @preconcurrency UNUser
@objc @objc
private func openDashboard() { private func openDashboard() {
if let serverManager = app?.serverManager, if let serverManager = app?.serverManager,
let url = URL(string: "http://localhost:\(serverManager.port)") { let url = URL(string: "http://localhost:\(serverManager.port)")
{
NSWorkspace.shared.open(url) NSWorkspace.shared.open(url)
} }
} }