diff --git a/supacode/Features/Terminal/TabBar/TerminalTabBarColors.swift b/supacode/Features/Terminal/TabBar/TerminalTabBarColors.swift index ec375851..8e73a81e 100644 --- a/supacode/Features/Terminal/TabBar/TerminalTabBarColors.swift +++ b/supacode/Features/Terminal/TabBar/TerminalTabBarColors.swift @@ -3,7 +3,7 @@ import SwiftUI enum TerminalTabBarColors { static var barBackground: Color { - Color(nsColor: .windowBackgroundColor) + adaptiveFill(dark: { .labelColor.withAlphaComponent(0.15) }, light: { .labelColor.withAlphaComponent(0.05) }) } // Selection is conveyed by a brightness ladder layered over `barBackground`, @@ -25,7 +25,7 @@ enum TerminalTabBarColors { } static var inactiveTabBackground: Color { - adaptiveFill(dark: { .labelColor.withAlphaComponent(0.035) }, light: { .clear }) + .clear } /// Resolves to `dark` under Dark Aqua and `light` otherwise, wrapped in a diff --git a/supacode/Features/Terminal/TabBar/TerminalTabBarMetrics.swift b/supacode/Features/Terminal/TabBar/TerminalTabBarMetrics.swift index 78e30e74..4e9de90f 100644 --- a/supacode/Features/Terminal/TabBar/TerminalTabBarMetrics.swift +++ b/supacode/Features/Terminal/TabBar/TerminalTabBarMetrics.swift @@ -2,18 +2,18 @@ import CoreGraphics enum TerminalTabBarMetrics { static let barHeight: CGFloat = 33 - static let barPadding: CGFloat = 0 + static let barPadding: CGFloat = 4 static let tabHeight: CGFloat = 32 static let tabMinWidth: CGFloat = 140 - static let tabMaxWidth: CGFloat = 220 static let tabCornerRadius: CGFloat = 0 static let tabSpacing: CGFloat = 0 + // Top/bottom inset applied to the inter-tab divider so it does not run the + // full bar height; the shorter line is centered by the row's HStack. + static let tabDividerVerticalInset: CGFloat = 6 static let tabHorizontalPadding: CGFloat = 12 static let contentSpacing: CGFloat = 6 static let contentTrailingSpacing: CGFloat = 4 static let activeIndicatorHeight: CGFloat = 2 - static let activeTabOffset: CGFloat = 0.5 - static let activeTabBottomPadding: CGFloat = 1 static let closeButtonSize: CGFloat = 16 static let dirtyIndicatorSize: CGFloat = 8 static let overflowShadowWidth: CGFloat = 24 diff --git a/supacode/Features/Terminal/TabBar/Views/TerminalTabBackground.swift b/supacode/Features/Terminal/TabBar/Views/TerminalTabBackground.swift index aadbf5b6..b0018b0f 100644 --- a/supacode/Features/Terminal/TabBar/Views/TerminalTabBackground.swift +++ b/supacode/Features/Terminal/TabBar/Views/TerminalTabBackground.swift @@ -16,12 +16,6 @@ struct TerminalTabBackground: View { TerminalTabBarColors.inactiveTabBackground } - if isActive { - Rectangle() - .fill(Color.accentColor) - .frame(height: TerminalTabBarMetrics.activeIndicatorHeight) - } - if !isActive { VStack(spacing: 0) { Spacer(minLength: 0) diff --git a/supacode/Features/Terminal/TabBar/Views/TerminalTabBarBackground.swift b/supacode/Features/Terminal/TabBar/Views/TerminalTabBarBackground.swift index af57996a..5c42ee37 100644 --- a/supacode/Features/Terminal/TabBar/Views/TerminalTabBarBackground.swift +++ b/supacode/Features/Terminal/TabBar/Views/TerminalTabBarBackground.swift @@ -7,8 +7,9 @@ struct TerminalTabBarBackground: View { private var surfaceTopChromeBackgroundOpacity var body: some View { - Rectangle() + Capsule() .fill(TerminalTabBarColors.barBackground.opacity(chromeBackgroundOpacity)) + .padding(.horizontal, TerminalTabBarMetrics.barPadding) } private var chromeBackgroundOpacity: Double { diff --git a/supacode/Features/Terminal/TabBar/Views/TerminalTabBarView.swift b/supacode/Features/Terminal/TabBar/Views/TerminalTabBarView.swift index e01cf0db..589c5499 100644 --- a/supacode/Features/Terminal/TabBar/Views/TerminalTabBarView.swift +++ b/supacode/Features/Terminal/TabBar/Views/TerminalTabBarView.swift @@ -28,6 +28,9 @@ struct TerminalTabBarView: View { closeAll: closeAll, hasNotification: hasNotification ) + // Background wraps only the tabs; the trailing accessories (+ / splits) + // sit outside it on the bare bar chrome. + .background(TerminalTabBarBackground()) Spacer(minLength: 0) TerminalTabBarTrailingAccessories( createTab: createTab, @@ -37,8 +40,69 @@ struct TerminalTabBarView: View { ) } .frame(height: TerminalTabBarMetrics.barHeight) - .background(TerminalTabBarBackground()) .saturation(activeState == .inactive ? 0 : 1) .clipped() } } + +// MARK: - Previews + +#if DEBUG + @MainActor + private struct TerminalTabBarPreviewRow: View { + let title: String + @State private var manager: TerminalTabManager + + init(title: String, tabCount: Int) { + self.title = title + let manager = TerminalTabManager() + for index in 0.. 0, count > 0 else { return nil } - let perTab = containerWidth / CGFloat(count) - return min( - TerminalTabBarMetrics.tabMaxWidth, - max(TerminalTabBarMetrics.tabMinWidth, perTab) - ) + // Tabs split the bar equally and fill it (no max cap). Once the equal share + // would dip below the minimum (too many tabs), pin to the minimum so the + // row overflows and scrolls instead. Account for the row's horizontal + // padding and inter-tab spacing so a filled row does not spuriously overflow. + let available = + containerWidth + - TerminalTabBarMetrics.barPadding * 2 + - TerminalTabBarMetrics.tabSpacing * CGFloat(count - 1) + let perTab = available / CGFloat(count) + return max(TerminalTabBarMetrics.tabMinWidth, perTab) } } diff --git a/supacode/Infrastructure/Ghostty/GhosttyShortcutManager.swift b/supacode/Infrastructure/Ghostty/GhosttyShortcutManager.swift index dfb415b1..41534c76 100644 --- a/supacode/Infrastructure/Ghostty/GhosttyShortcutManager.swift +++ b/supacode/Infrastructure/Ghostty/GhosttyShortcutManager.swift @@ -5,7 +5,7 @@ import SwiftUI @MainActor @Observable final class GhosttyShortcutManager { - private let runtime: GhosttyRuntime + private let runtime: GhosttyRuntime? private var generation: Int = 0 init(runtime: GhosttyRuntime) { @@ -15,18 +15,27 @@ final class GhosttyShortcutManager { } } + #if DEBUG + /// Preview/test instance with no runtime; shortcut lookups return nil so + /// views render without a live Ghostty app (mirrors + /// `WorktreeTerminalManager.preview`). + init(preview: Void) { + self.runtime = nil + } + #endif + func refresh() { generation += 1 } var commandPaletteEntries: [GhosttyCommand] { _ = generation - return runtime.commandPaletteEntries() + return runtime?.commandPaletteEntries() ?? [] } func keyboardShortcut(for action: String) -> KeyboardShortcut? { _ = generation - return runtime.keyboardShortcut(for: action) + return runtime?.keyboardShortcut(for: action) } func display(for action: String) -> String? {