From 79cfe2d1682443703671b03e4eedf6ea869e9c9f Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Wed, 4 Mar 2026 02:48:06 -0800 Subject: [PATCH] Fix cross-window theme background gating after jump-to-unread (#861) * Fix cross-window theme background gating * Handle owning-manager nil-selection theme edge case * Simplify window background gating helper --- Sources/GhosttyTerminalView.swift | 31 +++++++++- cmuxTests/GhosttyConfigTests.swift | 90 ++++++++++++++++++++++++++++++ 2 files changed, 120 insertions(+), 1 deletion(-) diff --git a/Sources/GhosttyTerminalView.swift b/Sources/GhosttyTerminalView.swift index 3de0ee2a..020d4ad7 100644 --- a/Sources/GhosttyTerminalView.swift +++ b/Sources/GhosttyTerminalView.swift @@ -2981,9 +2981,38 @@ class GhosttyNSView: NSView, NSUserInterfaceValidations { } } + // Theme/background application is window-local. During cross-window workspace + // switches (e.g. jump-to-unread), the global active tab manager can lag behind. + // Prefer the owning window's selected workspace when available. + static func shouldApplyWindowBackground( + surfaceTabId: UUID?, + owningManagerExists: Bool, + owningSelectedTabId: UUID?, + activeSelectedTabId: UUID? + ) -> Bool { + guard let surfaceTabId else { return true } + if owningManagerExists { + guard let owningSelectedTabId else { return true } + return owningSelectedTabId == surfaceTabId + } + if let activeSelectedTabId { + return activeSelectedTabId == surfaceTabId + } + return true + } + func applyWindowBackgroundIfActive() { guard let window else { return } - if let tabId, let selectedId = AppDelegate.shared?.tabManager?.selectedTabId, tabId != selectedId { + let appDelegate = AppDelegate.shared + let owningManager = tabId.flatMap { appDelegate?.tabManagerFor(tabId: $0) } + let owningSelectedTabId = owningManager?.selectedTabId + let activeSelectedTabId = owningManager == nil ? appDelegate?.tabManager?.selectedTabId : nil + guard Self.shouldApplyWindowBackground( + surfaceTabId: tabId, + owningManagerExists: owningManager != nil, + owningSelectedTabId: owningSelectedTabId, + activeSelectedTabId: activeSelectedTabId + ) else { return } applySurfaceBackground() diff --git a/cmuxTests/GhosttyConfigTests.swift b/cmuxTests/GhosttyConfigTests.swift index ac9d68b7..98bff922 100644 --- a/cmuxTests/GhosttyConfigTests.swift +++ b/cmuxTests/GhosttyConfigTests.swift @@ -659,6 +659,96 @@ final class WindowTransparencyDecisionTests: XCTestCase { } } +final class WindowBackgroundSelectionGateTests: XCTestCase { + func testShouldApplyWindowBackgroundUsesOwningWindowSelectionWhenAvailable() { + let tabId = UUID() + let activeSelectedTabId = UUID() + + XCTAssertTrue( + GhosttyNSView.shouldApplyWindowBackground( + surfaceTabId: tabId, + owningManagerExists: true, + owningSelectedTabId: tabId, + activeSelectedTabId: activeSelectedTabId + ) + ) + } + + func testShouldApplyWindowBackgroundRejectsWhenOwningSelectionDiffers() { + let tabId = UUID() + + XCTAssertFalse( + GhosttyNSView.shouldApplyWindowBackground( + surfaceTabId: tabId, + owningManagerExists: true, + owningSelectedTabId: UUID(), + activeSelectedTabId: tabId + ) + ) + } + + func testShouldApplyWindowBackgroundAllowsWhenOwningManagerSelectionIsTemporarilyNil() { + let tabId = UUID() + + XCTAssertTrue( + GhosttyNSView.shouldApplyWindowBackground( + surfaceTabId: tabId, + owningManagerExists: true, + owningSelectedTabId: nil, + activeSelectedTabId: UUID() + ) + ) + } + + func testShouldApplyWindowBackgroundFallsBackToActiveSelection() { + let tabId = UUID() + + XCTAssertTrue( + GhosttyNSView.shouldApplyWindowBackground( + surfaceTabId: tabId, + owningManagerExists: false, + owningSelectedTabId: nil, + activeSelectedTabId: tabId + ) + ) + XCTAssertFalse( + GhosttyNSView.shouldApplyWindowBackground( + surfaceTabId: tabId, + owningManagerExists: false, + owningSelectedTabId: nil, + activeSelectedTabId: UUID() + ) + ) + } + + func testShouldApplyWindowBackgroundAllowsWhenNoSelectionContext() { + XCTAssertTrue( + GhosttyNSView.shouldApplyWindowBackground( + surfaceTabId: UUID(), + owningManagerExists: false, + owningSelectedTabId: nil, + activeSelectedTabId: nil + ) + ) + XCTAssertTrue( + GhosttyNSView.shouldApplyWindowBackground( + surfaceTabId: nil, + owningManagerExists: false, + owningSelectedTabId: nil, + activeSelectedTabId: nil + ) + ) + XCTAssertTrue( + GhosttyNSView.shouldApplyWindowBackground( + surfaceTabId: nil, + owningManagerExists: true, + owningSelectedTabId: UUID(), + activeSelectedTabId: UUID() + ) + ) + } +} + final class NotificationBurstCoalescerTests: XCTestCase { func testSignalsInSameBurstFlushOnce() { let coalescer = NotificationBurstCoalescer(delay: 0.01)