cmux/Sources/Panels/TerminalPanelView.swift
Lawrence Chen cef1513ceb Fix notification ring not appearing on terminal panes
TerminalPanelView and PanelContentView held notificationStore as a
plain `let` property. Since it's a reference type (class), SwiftUI's
structural diffing saw no change when notifications were added and
skipped re-evaluating the view body. Changed to @ObservedObject var
so the views properly subscribe to the store's @Published changes.

Closes #126
2026-02-20 02:41:52 -08:00

73 lines
2.7 KiB
Swift

import SwiftUI
import Foundation
import AppKit
/// View for rendering a terminal panel
struct TerminalPanelView: View {
@ObservedObject var panel: TerminalPanel
let isFocused: Bool
let isVisibleInUI: Bool
let portalPriority: Int
let isSplit: Bool
let appearance: PanelAppearance
@ObservedObject var notificationStore: TerminalNotificationStore
let onFocus: () -> Void
let onTriggerFlash: () -> Void
var body: some View {
ZStack(alignment: .topLeading) {
GhosttyTerminalView(
terminalSurface: panel.surface,
isActive: isFocused,
isVisibleInUI: isVisibleInUI,
portalZPriority: portalPriority,
showsInactiveOverlay: isSplit && !isFocused,
inactiveOverlayColor: appearance.unfocusedOverlayNSColor,
inactiveOverlayOpacity: appearance.unfocusedOverlayOpacity,
reattachToken: panel.viewReattachToken,
onFocus: { _ in onFocus() },
onTriggerFlash: onTriggerFlash
)
// Keep the NSViewRepresentable identity stable across bonsplit structural updates.
// This prevents transient teardown/recreate that can momentarily detach the hosted terminal view.
.id(panel.id)
.background(Color.clear)
// Unread notification indicator
if notificationStore.hasUnreadNotification(forTabId: panel.workspaceId, surfaceId: panel.id) {
Rectangle()
.stroke(Color(nsColor: .systemBlue), lineWidth: 2.5)
.shadow(color: Color(nsColor: .systemBlue).opacity(0.35), radius: 3)
.padding(2)
.allowsHitTesting(false)
}
// Search overlay
if let searchState = panel.searchState {
SurfaceSearchOverlay(
surface: panel.surface,
searchState: searchState,
onClose: {
panel.searchState = nil
panel.hostedView.moveFocus()
}
)
}
}
}
}
/// Shared appearance settings for panels
struct PanelAppearance {
let dividerColor: Color
let unfocusedOverlayNSColor: NSColor
let unfocusedOverlayOpacity: Double
static func fromConfig(_ config: GhosttyConfig) -> PanelAppearance {
PanelAppearance(
dividerColor: Color(nsColor: config.resolvedSplitDividerColor),
unfocusedOverlayNSColor: config.unfocusedSplitOverlayFill,
unfocusedOverlayOpacity: config.unfocusedSplitOverlayOpacity
)
}
}