diff --git a/Sources/ContentView.swift b/Sources/ContentView.swift index 5900a69bc1f3..f001ce601cb3 100644 --- a/Sources/ContentView.swift +++ b/Sources/ContentView.swift @@ -9044,13 +9044,28 @@ private final class SidebarTabItemSettingsStore: ObservableObject { } } -private struct SidebarTabItemPresentationSnapshot: Equatable { +struct SidebarTabItemPresentationSnapshot: Equatable { let tabId: UUID let unreadCount: Int let latestNotificationText: String? let showsModifierShortcutHints: Bool } +struct SidebarTabItemPresentationResolutionPolicy { + static func resolved( + live: SidebarTabItemPresentationSnapshot, + frozen: SidebarTabItemPresentationSnapshot? + ) -> SidebarTabItemPresentationSnapshot { + guard let frozen, frozen.tabId == live.tabId else { return live } + return SidebarTabItemPresentationSnapshot( + tabId: live.tabId, + unreadCount: live.unreadCount, + latestNotificationText: live.latestNotificationText, + showsModifierShortcutHints: frozen.showsModifierShortcutHints + ) + } +} + struct VerticalTabsSidebar: View { @ObservedObject var updateViewModel: UpdateViewModel @ObservedObject var fileExplorerState: FileExplorerState @@ -9523,6 +9538,10 @@ struct VerticalTabsSidebar: View { let frozenPresentation = frozenTabItemPresentation?.tabId == tab.id ? frozenTabItemPresentation : nil + let resolvedPresentation = SidebarTabItemPresentationResolutionPolicy.resolved( + live: livePresentation, + frozen: frozenPresentation + ) return TabItemView( tabManager: tabManager, @@ -9537,13 +9556,13 @@ struct VerticalTabsSidebar: View { workspaceShortcutModifierSymbol: renderContext.workspaceNumberShortcut.numberedDigitHintPrefix, canCloseWorkspace: renderContext.canCloseWorkspace, accessibilityWorkspaceCount: renderContext.workspaceCount, - unreadCount: frozenPresentation?.unreadCount ?? liveUnreadCount, - latestNotificationText: frozenPresentation?.latestNotificationText ?? liveLatestNotificationText, + unreadCount: resolvedPresentation.unreadCount, + latestNotificationText: resolvedPresentation.latestNotificationText, rowSpacing: tabRowSpacing, setSelectionToTabs: { selection = .tabs }, selectedTabIds: $selectedTabIds, lastSidebarSelectionIndex: $lastSidebarSelectionIndex, - showsModifierShortcutHints: frozenPresentation?.showsModifierShortcutHints ?? liveShowsModifierShortcutHints, + showsModifierShortcutHints: resolvedPresentation.showsModifierShortcutHints, dragAutoScrollController: dragAutoScrollController, draggedTabId: $draggedTabId, dropIndicator: $dropIndicator, @@ -9926,7 +9945,9 @@ private enum FeedbackComposerClient { if let payload = try? JSONSerialization.jsonObject(with: data) as? [String: Any], let errorMessage = payload["error"] as? String, errorMessage.isEmpty == false { + #if DEBUG NSLog("feedback.submit.rejected status=%@ error=%@", String(httpResponse.statusCode), errorMessage) + #endif } throw FeedbackComposerSubmissionError.rejected(statusCode: httpResponse.statusCode) } diff --git a/cmuxTests/SidebarWorkspaceSnapshotRefreshPolicyTests.swift b/cmuxTests/SidebarWorkspaceSnapshotRefreshPolicyTests.swift index 4fd2ec28721a..0ad484332d8d 100644 --- a/cmuxTests/SidebarWorkspaceSnapshotRefreshPolicyTests.swift +++ b/cmuxTests/SidebarWorkspaceSnapshotRefreshPolicyTests.swift @@ -143,6 +143,73 @@ final class SidebarWorkspaceSnapshotRefreshPolicyTests: XCTestCase { } } +final class SidebarTabItemPresentationResolutionPolicyTests: XCTestCase { + func testFrozenContextMenuPresentationDoesNotSuppressLiveNotificationState() { + let tabId = UUID() + let frozen = SidebarTabItemPresentationSnapshot( + tabId: tabId, + unreadCount: 0, + latestNotificationText: nil, + showsModifierShortcutHints: true + ) + let live = SidebarTabItemPresentationSnapshot( + tabId: tabId, + unreadCount: 1, + latestNotificationText: "done", + showsModifierShortcutHints: false + ) + + let resolved = SidebarTabItemPresentationResolutionPolicy.resolved( + live: live, + frozen: frozen + ) + + XCTAssertEqual(resolved.unreadCount, 1) + XCTAssertEqual(resolved.latestNotificationText, "done") + XCTAssertTrue(resolved.showsModifierShortcutHints) + } + + func testNoFrozenPresentationUsesLiveSnapshot() { + let live = SidebarTabItemPresentationSnapshot( + tabId: UUID(), + unreadCount: 2, + latestNotificationText: "live", + showsModifierShortcutHints: true + ) + + let resolved = SidebarTabItemPresentationResolutionPolicy.resolved( + live: live, + frozen: nil + ) + + XCTAssertEqual(resolved, live) + } + + func testNonMatchingTabIdUsesLiveShortcutHints() { + let frozen = SidebarTabItemPresentationSnapshot( + tabId: UUID(), + unreadCount: 0, + latestNotificationText: nil, + showsModifierShortcutHints: true + ) + let live = SidebarTabItemPresentationSnapshot( + tabId: UUID(), + unreadCount: 1, + latestNotificationText: "done", + showsModifierShortcutHints: false + ) + + let resolved = SidebarTabItemPresentationResolutionPolicy.resolved( + live: live, + frozen: frozen + ) + + XCTAssertEqual(resolved.unreadCount, 1) + XCTAssertEqual(resolved.latestNotificationText, "done") + XCTAssertFalse(resolved.showsModifierShortcutHints) + } +} + final class SidebarWorkspaceRowInteractionStateTests: XCTestCase { func testHoverRevealIsIndependentFromStaleContextMenuVisibility() { var state = SidebarWorkspaceRowInteractionState()