From 7f03d7651333fdf56e2ee539ea3767fa50dd1d38 Mon Sep 17 00:00:00 2001 From: vanvasten Date: Fri, 16 Oct 2026 15:04:20 -0300 Subject: [PATCH 1/2] fix: fade the tab strip edges where tabs are hidden and keep the scroll position valid When the tab row overflows, the edge that hides tabs fades out. Closing tabs no longer leaves the strip scrolled past the end. The SwiftUI fallback strip keeps tabs at a readable minimum width. --- Sources/WindowPaneChromePortal.swift | 66 +++++++++++++++++- programaTests/WindowAndDragTests.swift | 67 +++++++++++++++++++ .../Internal/Styling/TabBarMetrics.swift | 4 +- .../Bonsplit/Internal/Views/TabItemView.swift | 2 + 4 files changed, 137 insertions(+), 2 deletions(-) diff --git a/Sources/WindowPaneChromePortal.swift b/Sources/WindowPaneChromePortal.swift index 35b2733da..fd918bd5d 100644 --- a/Sources/WindowPaneChromePortal.swift +++ b/Sources/WindowPaneChromePortal.swift @@ -494,7 +494,18 @@ final class WindowPaneChromePortalRegistry: NSObject, BonsplitPaneChromePortalBr var hostViewForTesting: NSView { hostView } func nativeTabFramesForTesting(in paneID: PaneID) -> [NSRect] { - bars[paneID]?.tabFramesForTesting ?? [] + bars[paneID]?.layoutSubtreeIfNeeded() + return bars[paneID]?.tabFramesForTesting ?? [] + } + + func nativeVisibleTabRectForTesting(in paneID: PaneID) -> NSRect { + bars[paneID]?.layoutSubtreeIfNeeded() + return bars[paneID]?.visibleDocumentRectForTesting ?? .zero + } + + func nativeTabDocumentWidthForTesting(in paneID: PaneID) -> CGFloat { + bars[paneID]?.layoutSubtreeIfNeeded() + return bars[paneID]?.documentWidthForTesting ?? 0 } func nativeTabInsertionIndexForTesting(in paneID: PaneID, documentX: CGFloat) -> Int? { @@ -595,7 +606,9 @@ private final class TabStripScrollView: NSScrollView { private final class NativePaneTabBarView: PaneChromeDragBackgroundView { private static let tabTransferPasteboardType = NSPasteboard.PasteboardType("com.splittabbar.tabtransfer") + private static let edgeFadeWidth: CGFloat = 24 private let scrollView = TabStripScrollView(frame: .zero) + private let edgeFadeMask = CAGradientLayer() // Unflipped on purpose: a flipped document view mirrors the glass pills' // built-in shadow upward (layer geometry flip flips shadowOffset), while // the control capsules in the unflipped host cast theirs downward. Layout @@ -649,6 +662,16 @@ private final class NativePaneTabBarView: PaneChromeDragBackgroundView { scrollView.horizontalScrollElasticity = .automatic scrollView.verticalScrollElasticity = .none scrollView.documentView = documentView + scrollView.wantsLayer = true + scrollView.contentView.postsBoundsChangedNotifications = true + NotificationCenter.default.addObserver( + self, + selector: #selector(clipBoundsDidChange), + name: NSView.boundsDidChangeNotification, + object: scrollView.contentView + ) + edgeFadeMask.startPoint = CGPoint(x: 0, y: 0.5) + edgeFadeMask.endPoint = CGPoint(x: 1, y: 0.5) addSubview(scrollView) documentView.addSubview(dropIndicator, positioned: .above, relativeTo: nil) registerForDraggedTypes([Self.tabTransferPasteboardType]) @@ -659,6 +682,8 @@ private final class NativePaneTabBarView: PaneChromeDragBackgroundView { @objc private func newTabPressed() { newTabAction?() } + @objc private func clipBoundsDidChange(_ note: Notification) { updateEdgeFades() } + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } override func layout() { @@ -737,7 +762,9 @@ private final class NativePaneTabBarView: PaneChromeDragBackgroundView { newTabButton.frame = NSRect(x: x, y: 0, width: plusWidth, height: height) x += plusWidth + gap documentView.frame = NSRect(x: 0, y: 0, width: max(x, scrollView.contentSize.width), height: height) + constrainClipOrigin() revealPendingTab() + updateEdgeFades() if let dropIndicatorIndex { positionDropIndicator(at: dropIndicatorIndex) } @@ -758,6 +785,43 @@ private final class NativePaneTabBarView: PaneChromeDragBackgroundView { scrollView.reflectScrolledClipView(clip) } + /// Closing tabs shrinks the document under a scrolled clip view; pull the + /// origin back inside the new bounds. + private func constrainClipOrigin() { + let clip = scrollView.contentView + let constrained = clip.constrainBoundsRect(clip.bounds).origin + guard constrained.x != clip.bounds.origin.x else { return } + clip.setBoundsOrigin(NSPoint(x: constrained.x, y: 0)) + scrollView.reflectScrolledClipView(clip) + } + + /// Fades the edge that has hidden tabs. The mask exists only while the row + /// overflows so a row that fits renders exactly as before. + private func updateEdgeFades() { + let bounds = scrollView.contentView.bounds + let hasLeading = bounds.minX > 0.5 + let hasTrailing = bounds.maxX < documentView.frame.width - 0.5 + let width = scrollView.bounds.width + CATransaction.begin() + CATransaction.setDisableActions(true) + defer { CATransaction.commit() } + guard hasLeading || hasTrailing, width > Self.edgeFadeWidth * 2 else { + scrollView.layer?.mask = nil + return + } + let fraction = Self.edgeFadeWidth / width + let opaque = NSColor.black.cgColor + let clear = NSColor.clear.cgColor + edgeFadeMask.frame = scrollView.bounds + edgeFadeMask.colors = [hasLeading ? clear : opaque, opaque, opaque, hasTrailing ? clear : opaque] + edgeFadeMask.locations = [0, fraction, 1 - fraction, 1].map { NSNumber(value: Double($0)) } + scrollView.layer?.mask = edgeFadeMask + } + + fileprivate var visibleDocumentRectForTesting: NSRect { scrollView.contentView.bounds } + + fileprivate var documentWidthForTesting: CGFloat { documentView.frame.width } + fileprivate var tabFramesForTesting: [NSRect] { guard let descriptor else { return [] } return descriptor.tabs.compactMap { pillViews[$0.id]?.frame } diff --git a/programaTests/WindowAndDragTests.swift b/programaTests/WindowAndDragTests.swift index 93351adb7..0af2129d8 100644 --- a/programaTests/WindowAndDragTests.swift +++ b/programaTests/WindowAndDragTests.swift @@ -209,6 +209,73 @@ final class WindowGlassEffectTests: XCTestCase { throw XCTSkip("Native Liquid Glass requires the macOS 26 SDK and runtime") } + func testNativePaneTabBarKeepsTitlesReadableAndScrollsSelectedTabIntoView() throws { + #if compiler(>=6.2) + if #available(macOS 26.0, *) { + _ = NSApplication.shared + let root = NSView(frame: NSRect(x: 0, y: 0, width: 360, height: 320)) + let terminalHost = WindowTerminalHostView(frame: root.bounds) + let anchor = NSView(frame: NSRect(x: 20, y: 270, width: 300, height: 40)) + root.addSubview(anchor) + root.addSubview(terminalHost, positioned: .above, relativeTo: anchor) + let window = NSWindow( + contentRect: root.bounds, + styleMask: [.titled, .closable, .resizable], + backing: .buffered, + defer: false + ) + defer { window.orderOut(nil) } + window.contentView = root + + let bridge = WindowPaneChromePortalRegistry.bridge(for: window) + let paneID = PaneID() + let ids = (0..<15).map { _ in TabID() } + func publish(selected: Int) { + let tabs = ids.enumerated().map { index, id in + BonsplitPaneChromeTabDescriptor( + id: id, title: "A long terminal title number \(index)", icon: "terminal", + iconImageData: nil, isSelected: index == selected, isPinned: false, + isDirty: false, showsNotificationBadge: false, accessibilityValue: "", + menuItems: [] + ) + } + bridge.updatePaneChrome(BonsplitPaneChromeDescriptor( + paneID: paneID, anchorView: anchor, tabs: tabs, isFocused: true, + isVisible: true, leadingInset: 0, showsSplitButtons: false, + onSelect: { _ in }, onClose: { _ in }, onContextAction: { _, _ in }, + dragPasteboardData: { _ in nil }, onDragStateChanged: { _, _ in }, + validatedDropIndex: { (0...tabs.count).contains($0) ? $0 : nil }, + onDropTab: { _ in true }, + onNewTab: {}, onSplitRight: {}, onSplitDown: {} + )) + root.layoutSubtreeIfNeeded() + } + + publish(selected: 0) + let readableMinimumWidth: CGFloat = 78 + let frames = bridge.nativeTabFramesForTesting(in: paneID) + XCTAssertEqual(frames.count, ids.count) + for frame in frames { + XCTAssertGreaterThanOrEqual(frame.width, readableMinimumWidth) + } + let visible = bridge.nativeVisibleTabRectForTesting(in: paneID) + XCTAssertGreaterThan(visible.width, 0) + XCTAssertGreaterThan(bridge.nativeTabDocumentWidthForTesting(in: paneID), visible.width) + XCTAssertLessThanOrEqual(try XCTUnwrap(frames.first).minX, visible.minX + 0.5) + + publish(selected: ids.count - 1) + let scrolledVisible = bridge.nativeVisibleTabRectForTesting(in: paneID) + let lastFrame = try XCTUnwrap(bridge.nativeTabFramesForTesting(in: paneID).last) + XCTAssertGreaterThan(scrolledVisible.minX, 0) + XCTAssertGreaterThanOrEqual(lastFrame.minX, scrolledVisible.minX) + XCTAssertLessThanOrEqual(lastFrame.maxX, scrolledVisible.maxX) + return + } + #endif + + throw XCTSkip("Native Liquid Glass requires the macOS 26 SDK and runtime") + } + func testNativePaneTabInsertionIndicatorStaysInsideDocumentBounds() throws { #if compiler(>=6.2) let bounds = NSRect(x: 0, y: 0, width: 240, height: 28) diff --git a/vendor/bonsplit/Sources/Bonsplit/Internal/Styling/TabBarMetrics.swift b/vendor/bonsplit/Sources/Bonsplit/Internal/Styling/TabBarMetrics.swift index 410fd44a2..5587f8c04 100644 --- a/vendor/bonsplit/Sources/Bonsplit/Internal/Styling/TabBarMetrics.swift +++ b/vendor/bonsplit/Sources/Bonsplit/Internal/Styling/TabBarMetrics.swift @@ -19,7 +19,9 @@ enum TabBarMetrics { // MARK: - Individual Tabs static var tabHeight: CGFloat { resolved("tabBarHeight", default: 26) } - static let tabMinWidth: CGFloat = 48 + /// Floor for a tab's width: below it a title shows only a letter or two, so + /// the strip scrolls instead of squeezing tabs further. + static let tabMinWidth: CGFloat = 120 static let tabMaxWidth: CGFloat = 220 static let tabCornerRadius: CGFloat = 0 static var tabHorizontalPadding: CGFloat { resolved("tabHorizontalPadding", default: 8) } diff --git a/vendor/bonsplit/Sources/Bonsplit/Internal/Views/TabItemView.swift b/vendor/bonsplit/Sources/Bonsplit/Internal/Views/TabItemView.swift index 7a599aa19..ab5838e1d 100644 --- a/vendor/bonsplit/Sources/Bonsplit/Internal/Views/TabItemView.swift +++ b/vendor/bonsplit/Sources/Bonsplit/Internal/Views/TabItemView.swift @@ -193,6 +193,8 @@ struct TabItemView: View { minHeight: TabBarMetrics.tabHeight, maxHeight: TabBarMetrics.tabHeight ) + // Size to the title within [min, max]; the strip's proposal never shrinks a tab. + .fixedSize(horizontal: true, vertical: false) .padding(.bottom, isSelected ? 1 : 0) .background(tabBackground.saturation(saturation)) .animation(.easeOut(duration: 0.14), value: showsShortcutHint) From 9dec7cbdf31c599bfca07e4a721bcfa3932b73ce Mon Sep 17 00:00:00 2001 From: vanvasten Date: Fri, 16 Oct 2026 15:05:15 -0300 Subject: [PATCH 2/2] refactor: keep terminal tab titles live instead of deriving them from branch or PR Drops the automatic "PR #N" and ticket-id titles so a terminal tab shows its live process title. --- Resources/Localizable.xcstrings | 17 -- Sources/Workspace.swift | 30 ---- programaTests/WorkspaceUnitTests.swift | 226 +++++-------------------- 3 files changed, 40 insertions(+), 233 deletions(-) diff --git a/Resources/Localizable.xcstrings b/Resources/Localizable.xcstrings index 9aa55cc7b..f6f05924f 100644 --- a/Resources/Localizable.xcstrings +++ b/Resources/Localizable.xcstrings @@ -14944,23 +14944,6 @@ } } }, - "tabBar.autoTitle.pullRequest": { - "extractionState": "manual", - "localizations": { - "en": { - "stringUnit": { - "state": "translated", - "value": "PR #%lld" - } - }, - "ja": { - "stringUnit": { - "state": "translated", - "value": "PR #%lld" - } - } - } - }, "tabBar.newTerminalTab": { "extractionState": "manual", "localizations": { diff --git a/Sources/Workspace.swift b/Sources/Workspace.swift index 4926ebe82..4cfd10061 100644 --- a/Sources/Workspace.swift +++ b/Sources/Workspace.swift @@ -831,39 +831,9 @@ final class Workspace: Identifiable, ObservableObject { !custom.isEmpty { return custom } - if let pullRequest = panelPullRequests[panelId] { - let panelBranch = normalizedSidebarBranchName(panelGitBranches[panelId]?.branch) - let pullRequestBranch = normalizedSidebarBranchName(pullRequest.branch) - if pullRequestBranch == nil || panelBranch == pullRequestBranch { - return String( - localized: "tabBar.autoTitle.pullRequest", - defaultValue: "PR #\(pullRequest.number)" - ) - } - } - if let branch = normalizedSidebarBranchName(panelGitBranches[panelId]?.branch) { - return Self.ticketIdentifier(in: branch) ?? branch - } return fallbackTitle } - private static let ticketIdentifierPattern = try? NSRegularExpression( - pattern: #"(? String? { - guard let ticketIdentifierPattern, - let match = ticketIdentifierPattern.firstMatch( - in: branch, - range: NSRange(branch.startIndex..., in: branch) - ), - match.numberOfRanges > 1, - let range = Range(match.range(at: 1), in: branch) else { - return nil - } - return String(branch[range]) - } - func syncResolvedPanelTitle(panelId: UUID) { guard let panel = panels[panelId], let tabId = surfaceIdFromPanelId(panelId) else { return } let baseTitle = panelTitles[panelId] ?? panel.displayTitle diff --git a/programaTests/WorkspaceUnitTests.swift b/programaTests/WorkspaceUnitTests.swift index 77d5fa868..6c9dac271 100644 --- a/programaTests/WorkspaceUnitTests.swift +++ b/programaTests/WorkspaceUnitTests.swift @@ -3066,7 +3066,7 @@ final class WorkspacePanelGitBranchTests: XCTestCase { XCTAssertEqual(tab.title, expected, file: file, line: line) } - func testPanelTitleUsesPRTicketBranchManualPrecedenceWithoutRenamingWorkspace() throws { + func testPanelTitleKeepsLiveTitleWhenBranchAndPullRequestArriveWithoutRenamingWorkspace() throws { let workspace = Workspace() let panelId = try XCTUnwrap(workspace.focusedPanelId) let pullRequestURL = try XCTUnwrap(URL(string: "https://github.com/darkroomengineering/programa/pull/482")) @@ -3080,9 +3080,6 @@ final class WorkspacePanelGitBranchTests: XCTestCase { branch: "feature/PROG-123-summary", isDirty: false ) - assertPanelAndTabTitle("PROG-123", workspace: workspace, panelId: panelId) - XCTAssertEqual(workspace.title, workspaceTitle) - workspace.updatePanelPullRequest( panelId: panelId, number: 482, @@ -3091,167 +3088,54 @@ final class WorkspacePanelGitBranchTests: XCTestCase { status: .open, branch: "feature/PROG-123-summary" ) - assertPanelAndTabTitle("PR #482", workspace: workspace, panelId: panelId) - XCTAssertEqual(workspace.title, workspaceTitle) - - workspace.setPanelCustomTitle(panelId: panelId, title: " Manual lane ") - assertPanelAndTabTitle("Manual lane", workspace: workspace, panelId: panelId) - - workspace.updatePanelGitBranch( - panelId: panelId, - branch: "feature/OPS9-77-follow-up", - isDirty: true - ) - workspace.updatePanelPullRequest( - panelId: panelId, - number: 483, - label: "Follow-up", - url: pullRequestURL, - status: .open, - branch: "feature/OPS9-77-follow-up" - ) - assertPanelAndTabTitle("Manual lane", workspace: workspace, panelId: panelId) - - workspace.setPanelCustomTitle(panelId: panelId, title: nil) - assertPanelAndTabTitle("PR #483", workspace: workspace, panelId: panelId) - - workspace.clearPanelPullRequest(panelId: panelId) - assertPanelAndTabTitle("OPS9-77", workspace: workspace, panelId: panelId) - - workspace.clearPanelGitBranch(panelId: panelId) assertPanelAndTabTitle("agent-shell", workspace: workspace, panelId: panelId) XCTAssertEqual( workspace.title, workspaceTitle, - "Panel metadata must not rename the workspace shown in the sidebar" - ) - } - - func testAutomaticPanelTitleUsesOnlyConservativeUppercaseTicketTokens() throws { - let workspace = Workspace() - let panelId = try XCTUnwrap(workspace.focusedPanelId) - XCTAssertTrue(workspace.updatePanelTitle(panelId: panelId, title: "live-process")) - let workspaceTitle = workspace.title - - workspace.updatePanelGitBranch( - panelId: panelId, - branch: " feature/lowercase-summary ", - isDirty: false - ) - assertPanelAndTabTitle( - "feature/lowercase-summary", - workspace: workspace, - panelId: panelId - ) - - workspace.updatePanelGitBranch( - panelId: panelId, - branch: "feature/prog-123-summary", - isDirty: false - ) - assertPanelAndTabTitle( - "feature/prog-123-summary", - workspace: workspace, - panelId: panelId - ) - - workspace.updatePanelGitBranch( - panelId: panelId, - branch: "feature/PROG-123-summary", - isDirty: false + "Branch and pull request metadata must not rename the workspace shown in the sidebar" ) - assertPanelAndTabTitle("PROG-123", workspace: workspace, panelId: panelId) - XCTAssertEqual(workspace.title, workspaceTitle) - } - func testPanelTitleUsesOnlyPullRequestMetadataScopedToTheCurrentBranch() throws { - let workspace = Workspace() - let panelId = try XCTUnwrap(workspace.focusedPanelId) - let pullRequestURL = try XCTUnwrap(URL(string: "https://github.com/darkroomengineering/programa/pull/484")) + workspace.setPanelCustomTitle(panelId: panelId, title: " Manual lane ") + assertPanelAndTabTitle("Manual lane", workspace: workspace, panelId: panelId) - XCTAssertTrue(workspace.updatePanelTitle(panelId: panelId, title: "live-process")) - workspace.updatePanelPullRequest( - panelId: panelId, - number: 484, - label: "Other lane", - url: pullRequestURL, - status: .open, - branch: "feature/OTHER-484-summary" - ) - assertPanelAndTabTitle( - "live-process", - workspace: workspace, - panelId: panelId - ) + workspace.setPanelCustomTitle(panelId: panelId, title: nil) + assertPanelAndTabTitle("agent-shell", workspace: workspace, panelId: panelId) - workspace.updatePanelGitBranch( - panelId: panelId, - branch: "feature/CURRENT-21-summary", - isDirty: false - ) - assertPanelAndTabTitle("CURRENT-21", workspace: workspace, panelId: panelId) + XCTAssertTrue(workspace.updatePanelTitle(panelId: panelId, title: "claude")) + assertPanelAndTabTitle("claude", workspace: workspace, panelId: panelId) + workspace.clearPanelPullRequest(panelId: panelId) workspace.clearPanelGitBranch(panelId: panelId) - workspace.updatePanelPullRequest( - panelId: panelId, - number: 485, - label: "Unscoped lane", - url: pullRequestURL, - status: .open - ) - assertPanelAndTabTitle( - "PR #485", - workspace: workspace, - panelId: panelId, - file: #filePath, - line: #line - ) + assertPanelAndTabTitle("claude", workspace: workspace, panelId: panelId) } - func testResetSidebarContextClearsAutomaticPanelTitlesButPreservesManualNames() throws { + func testTabsInTheSameRepositoryKeepTheirOwnLiveTitles() throws { let workspace = Workspace() - let panelId = try XCTUnwrap(workspace.focusedPanelId) - let pullRequestURL = try XCTUnwrap(URL(string: "https://github.com/darkroomengineering/programa/pull/486")) - - XCTAssertTrue(workspace.updatePanelTitle(panelId: panelId, title: "live-process")) - workspace.updatePanelGitBranch( - panelId: panelId, - branch: "feature/RESET-12-summary", - isDirty: false - ) - workspace.updatePanelPullRequest( - panelId: panelId, - number: 486, - label: "Reset lane", - url: pullRequestURL, - status: .open, - branch: "feature/RESET-12-summary" - ) - assertPanelAndTabTitle("PR #486", workspace: workspace, panelId: panelId) - - workspace.resetSidebarContext(reason: "test") - assertPanelAndTabTitle("live-process", workspace: workspace, panelId: panelId) - - workspace.setPanelCustomTitle(panelId: panelId, title: "Manual lane") - workspace.updatePanelGitBranch( - panelId: panelId, - branch: "feature/RESET-13-summary", - isDirty: false - ) - workspace.updatePanelPullRequest( - panelId: panelId, - number: 487, - label: "Reset manual lane", - url: pullRequestURL, - status: .open, - branch: "feature/RESET-13-summary" - ) + let firstPanelId = try XCTUnwrap(workspace.focusedPanelId) + let secondPanel = try XCTUnwrap( + workspace.newTerminalSplit(from: firstPanelId, orientation: .horizontal, focus: false) + ) + let pullRequestURL = try XCTUnwrap(URL(string: "https://github.com/darkroomengineering/programa/pull/840")) + + XCTAssertTrue(workspace.updatePanelTitle(panelId: firstPanelId, title: "claude")) + XCTAssertTrue(workspace.updatePanelTitle(panelId: secondPanel.id, title: "vim README.md")) + for panelId in [firstPanelId, secondPanel.id] { + workspace.updatePanelGitBranch(panelId: panelId, branch: "fix/shared-branch", isDirty: false) + workspace.updatePanelPullRequest( + panelId: panelId, + number: 840, + label: "Shared lane", + url: pullRequestURL, + status: .open, + branch: "fix/shared-branch" + ) + } - workspace.resetSidebarContext(reason: "test-manual") - assertPanelAndTabTitle("Manual lane", workspace: workspace, panelId: panelId) + assertPanelAndTabTitle("claude", workspace: workspace, panelId: firstPanelId) + assertPanelAndTabTitle("vim README.md", workspace: workspace, panelId: secondPanel.id) } - func testDetachAttachDoesNotPersistAutomaticPanelTitleButPreservesManualName() throws { + func testDetachAttachPreservesLiveAndManualPanelTitles() throws { let source = Workspace() let destination = Workspace() let finalDestination = Workspace() @@ -3261,7 +3145,6 @@ final class WorkspacePanelGitBranchTests: XCTestCase { finalDestination.teardownAllPanels() } let panelId = try XCTUnwrap(source.focusedPanelId) - let pullRequestURL = try XCTUnwrap(URL(string: "https://github.com/darkroomengineering/programa/pull/488")) XCTAssertTrue(source.updatePanelTitle(panelId: panelId, title: "live-process")) source.updatePanelGitBranch( @@ -3269,27 +3152,14 @@ final class WorkspacePanelGitBranchTests: XCTestCase { branch: "feature/MOVE-33-summary", isDirty: false ) - source.updatePanelPullRequest( - panelId: panelId, - number: 488, - label: "Move lane", - url: pullRequestURL, - status: .open, - branch: "feature/MOVE-33-summary" - ) - assertPanelAndTabTitle("PR #488", workspace: source, panelId: panelId) - let detachedAutomatic = try XCTUnwrap(source.detachSurface(panelId: panelId)) + let detachedLive = try XCTUnwrap(source.detachSurface(panelId: panelId)) let destinationPane = try XCTUnwrap(destination.bonsplitController.allPaneIds.first) XCTAssertEqual( - destination.attachDetachedSurface(detachedAutomatic, inPane: destinationPane, focus: false), + destination.attachDetachedSurface(detachedLive, inPane: destinationPane, focus: false), panelId ) - assertPanelAndTabTitle( - "live-process", - workspace: destination, - panelId: panelId - ) + assertPanelAndTabTitle("live-process", workspace: destination, panelId: panelId) destination.setPanelCustomTitle(panelId: panelId, title: "Manual moved lane") let detachedManual = try XCTUnwrap(destination.detachSurface(panelId: panelId)) @@ -3298,14 +3168,10 @@ final class WorkspacePanelGitBranchTests: XCTestCase { finalDestination.attachDetachedSurface(detachedManual, inPane: finalPane, focus: false), panelId ) - assertPanelAndTabTitle( - "Manual moved lane", - workspace: finalDestination, - panelId: panelId - ) + assertPanelAndTabTitle("Manual moved lane", workspace: finalDestination, panelId: panelId) } - func testSessionRestoreKeepsRawLiveTitleSeparateFromAutomaticAndManualTitles() throws { + func testSessionRestoreKeepsLiveTitleSeparateFromManualTitle() throws { let source = Workspace() let restored = Workspace() defer { @@ -3324,26 +3190,14 @@ final class WorkspacePanelGitBranchTests: XCTestCase { let snapshot = source.sessionSnapshot(includeScrollback: false) let panelSnapshot = try XCTUnwrap(snapshot.panels.first { $0.id == panelId }) - XCTAssertEqual( - panelSnapshot.title, - "live-process", - "The persisted base title must remain the live title so transient metadata cannot become stale state" - ) + XCTAssertEqual(panelSnapshot.title, "live-process") XCTAssertEqual(panelSnapshot.customTitle, "Manual restored lane") - XCTAssertEqual(panelSnapshot.gitBranch?.branch, "feature/RESTORE-91-summary") restored.restoreSessionSnapshot(snapshot) let restoredPanelId = try XCTUnwrap(restored.focusedPanelId) - assertPanelAndTabTitle( - "Manual restored lane", - workspace: restored, - panelId: restoredPanelId - ) + assertPanelAndTabTitle("Manual restored lane", workspace: restored, panelId: restoredPanelId) restored.setPanelCustomTitle(panelId: restoredPanelId, title: nil) - assertPanelAndTabTitle("RESTORE-91", workspace: restored, panelId: restoredPanelId) - - restored.clearPanelGitBranch(panelId: restoredPanelId) assertPanelAndTabTitle("live-process", workspace: restored, panelId: restoredPanelId) }