From 91bd2313fe4ee9ac3d15ceda671d01312bd6ae0c Mon Sep 17 00:00:00 2001 From: austinpower1258 Date: Mon, 22 Jun 2026 17:27:05 -0700 Subject: [PATCH 1/2] Revert sidebar row-height layout feedback This reverts commit 0df6f71db47474e5ec2623bb55eaff56b088dce3. PR #6558 regressed sidebar row heights and made workspace rows and terminal tab pills too wide/tall for their content. Reopen the original #6556 concern for a safer follow-up. --- Sources/ContentView.swift | 78 +-- .../SidebarWorkspaceGroupHeaderMetrics.swift | 5 - Sources/SidebarWorkspaceGroupHeaderView.swift | 19 +- Sources/SidebarWorkspaceRowDropMetrics.swift | 464 ------------------ cmux.xcodeproj/project.pbxproj | 8 - .../SidebarWorkspaceDropMetricsTests.swift | 279 ----------- 6 files changed, 54 insertions(+), 799 deletions(-) delete mode 100644 Sources/SidebarWorkspaceRowDropMetrics.swift delete mode 100644 cmuxTests/SidebarWorkspaceDropMetricsTests.swift diff --git a/Sources/ContentView.swift b/Sources/ContentView.swift index c6cca57fc969..5f624f7a7276 100644 --- a/Sources/ContentView.swift +++ b/Sources/ContentView.swift @@ -1679,7 +1679,7 @@ struct ContentView: View { VerticalTabsSidebar( updateViewModel: updateViewModel, fileExplorerState: fileExplorerState, - windowId: windowId, sidebarWidth: sidebarWidth, + windowId: windowId, onSendFeedback: presentFeedbackComposer, onToggleSidebar: { sidebarState.toggle() }, onNewTab: { @@ -10008,7 +10008,6 @@ struct VerticalTabsSidebar: View { var updateViewModel: UpdateStateModel @ObservedObject var fileExplorerState: FileExplorerState let windowId: UUID - let sidebarWidth: CGFloat let onSendFeedback: () -> Void let onToggleSidebar: () -> Void let onNewTab: () -> Void @@ -10378,7 +10377,7 @@ struct VerticalTabsSidebar: View { let workspaceCount: Int let canCloseWorkspace: Bool let workspaceNumberShortcut: StoredShortcut - let tabItemSettings: SidebarTabItemSettingsSnapshot, sidebarWidth: CGFloat + let tabItemSettings: SidebarTabItemSettingsSnapshot let tabIndexById: [UUID: Int] let workspaceById: [UUID: Workspace] let workspaceGroupIdByWorkspaceId: [UUID: UUID?] @@ -10440,7 +10439,7 @@ struct VerticalTabsSidebar: View { workspaceCount: workspaceCount, canCloseWorkspace: canCloseWorkspace, workspaceNumberShortcut: workspaceNumberShortcut, - tabItemSettings: tabItemSettings, sidebarWidth: sidebarWidth, + tabItemSettings: tabItemSettings, tabIndexById: tabIndexById, workspaceById: workspaceById, workspaceGroupIdByWorkspaceId: workspaceGroupIdByWorkspaceId, @@ -12141,7 +12140,6 @@ struct VerticalTabsSidebar: View { ) } - let rowSidebarWidth = renderContext.sidebarWidth - (tab.groupId != nil ? SidebarWorkspaceGroupingMetrics.memberIndent : 0) let row = TabItemView( tabManager: tabManager, notificationStore: notificationStore, @@ -12156,7 +12154,7 @@ struct VerticalTabsSidebar: View { accessibilityWorkspaceCount: renderContext.workspaceCount, unreadCount: liveUnreadCount, latestNotificationText: liveLatestNotificationText, - rowSpacing: tabRowSpacing, sidebarWidth: rowSidebarWidth, + rowSpacing: tabRowSpacing, setSelectionToTabs: { selection = .tabs }, selectedTabIds: $selectedTabIds, lastSidebarSelectionIndex: $lastSidebarSelectionIndex, @@ -12954,7 +12952,7 @@ struct TabItemView: View, Equatable { lhs.accessibilityWorkspaceCount == rhs.accessibilityWorkspaceCount && lhs.unreadCount == rhs.unreadCount && lhs.latestNotificationText == rhs.latestNotificationText && - lhs.rowSpacing == rhs.rowSpacing && lhs.sidebarWidth == rhs.sidebarWidth && + lhs.rowSpacing == rhs.rowSpacing && lhs.showsModifierShortcutHints == rhs.showsModifierShortcutHints && lhs.contextMenuWorkspaceIds == rhs.contextMenuWorkspaceIds && lhs.remoteContextMenuWorkspaceIds == rhs.remoteContextMenuWorkspaceIds && @@ -12981,7 +12979,7 @@ struct TabItemView: View, Equatable { let accessibilityWorkspaceCount: Int let unreadCount: Int let latestNotificationText: String? - let rowSpacing: CGFloat, sidebarWidth: CGFloat + let rowSpacing: CGFloat let setSelectionToTabs: () -> Void @Binding var selectedTabIds: Set @Binding var lastSidebarSelectionIndex: Int? @@ -12996,9 +12994,9 @@ struct TabItemView: View, Equatable { let isBeingDragged: Bool let topDropIndicatorVisible: Bool let onDragStart: () -> NSItemProvider - /// Factory invoked from `body` with a stable drop-hit height. Closure + /// Factory invoked from `body` with the row's measured `rowHeight`. Closure /// captures the parent's `dragState`, so TabItemView itself never holds an - /// `@Observable` store reference or layout-driven state (snapshot-boundary rule). + /// `@Observable` store reference (snapshot-boundary rule). let tabDropDelegateFactory: (CGFloat) -> SidebarTabDropDelegate let contextMenuWorkspaceIds: [UUID] let remoteContextMenuWorkspaceIds: [UUID] @@ -13018,10 +13016,10 @@ struct TabItemView: View, Equatable { @State private var observedIsActive: Bool? @StateObject private var contextMenuState = SidebarTabItemContextMenuState() @State private var rowInteractionState = SidebarWorkspaceRowInteractionState() - @State private var metadataRowsExpanded = false - @State private var metadataBlocksExpanded = false + @State private var rowHeight: CGFloat = 1 @State private var workspaceFinderDirectoryOpenRequest: WorkspaceFinderDirectoryOpenRequest? + private static let maxWrappedTitleLines = 8 private static let maxDisplayedTitleCharacters = 2048 var isMultiSelected: Bool { @@ -13254,6 +13252,18 @@ struct TabItemView: View, Equatable { } } + private var rowHeightProbe: some View { + GeometryReader { proxy in + Color.clear + .onAppear { + rowHeight = max(proxy.size.height, 1) + } + .onChange(of: proxy.size.height) { newHeight in + rowHeight = max(newHeight, 1) + } + } + } + @ViewBuilder private var remoteWorkspaceSection: some View { let workspaceSnapshot = self.workspaceSnapshot @@ -13357,20 +13367,11 @@ struct TabItemView: View, Equatable { : nil let effectiveSubtitle = latestNotificationSubtitle ?? conversationMessageSubtitle let detailVisibility = visibleAuxiliaryDetails - let titleLineLimit = settings.wrapsWorkspaceTitles ? SidebarWorkspaceRowDropMetrics.maxWrappedTitleLines : 1 + let titleLineLimit = settings.wrapsWorkspaceTitles ? Self.maxWrappedTitleLines : 1 let displayedTitle = workspaceSnapshot.title.sidebarBoundedDisplayString( maxDisplayedLines: titleLineLimit, maxDisplayedCharacters: Self.maxDisplayedTitleCharacters ) - let dropTargetHeight = SidebarWorkspaceRowDropMetrics.dropTargetHeight( - snapshot: workspaceSnapshot, - settings: settings, - effectiveSubtitle: effectiveSubtitle, - metadataEntryIsExpanded: metadataRowsExpanded, - metadataBlocksAreExpanded: metadataBlocksExpanded, - sidebarWidth: sidebarWidth, unreadCount: unreadCount, - canCloseWorkspace: canCloseWorkspace - ) let scaledUnreadBadgeSize = 16 * fontScale let scaledCloseButtonHitSize = max(16, 16 * fontScale) let scaledCloseButtonWidth = max( @@ -13487,7 +13488,6 @@ struct TabItemView: View, Equatable { if !metadataEntries.isEmpty { SidebarMetadataRows( entries: metadataEntries, - isExpanded: $metadataRowsExpanded, isActive: usesInvertedActiveForeground, activeForegroundColor: activeSecondaryColor(0.95), activeSecondaryForegroundColor: activeSecondaryColor(0.65), @@ -13499,7 +13499,6 @@ struct TabItemView: View, Equatable { if !metadataBlocks.isEmpty { SidebarMetadataMarkdownBlocks( blocks: metadataBlocks, - isExpanded: $metadataBlocksExpanded, isActive: usesInvertedActiveForeground, activeForegroundColor: activeSecondaryColor(0.8), activeSecondaryForegroundColor: activeSecondaryColor(0.65), @@ -13733,6 +13732,7 @@ struct TabItemView: View, Equatable { ) .shortcutHintVisibilityAnimation(value: showsWorkspaceShortcutHint) .padding(.horizontal, SidebarWorkspaceListMetrics.rowOuterHorizontalPadding) + .background { rowHeightProbe } .contentShape(Rectangle()) .opacity(isBeingDragged ? 0.6 : 1) .overlay { @@ -13812,7 +13812,7 @@ struct TabItemView: View, Equatable { } .onDrag(onDragStart) .internalOnlyTabDrag() - .onDrop(of: SidebarTabDragPayload.dropContentTypes, delegate: tabDropDelegateFactory(dropTargetHeight)) + .onDrop(of: SidebarTabDragPayload.dropContentTypes, delegate: tabDropDelegateFactory(rowHeight)) .onDrop(of: BonsplitTabDragPayload.dropContentTypes, delegate: SidebarBonsplitTabDropDelegate( targetWorkspaceId: tab.id, tabManager: tabManager, @@ -14937,7 +14937,7 @@ private struct SidebarWorkspaceDescriptionText: View { let isActive: Bool let activeForegroundColor: Color let fontScale: CGFloat - private static let maxDisplayedLines = SidebarWorkspaceRowDropMetrics.maxDescriptionLines + private static let maxDisplayedLines = 12 private static let maxDisplayedCharacters = 4096 var body: some View { @@ -15034,13 +15034,15 @@ private extension String { private struct SidebarMetadataRows: View { let entries: [SidebarStatusEntry] - @Binding var isExpanded: Bool let isActive: Bool let activeForegroundColor: Color let activeSecondaryForegroundColor: Color let fontScale: CGFloat let onFocus: () -> Void + @State private var isExpanded: Bool = false + private let collapsedEntryLimit = 3 + var body: some View { VStack(alignment: .leading, spacing: 2) { ForEach(visibleEntries, id: \.key) { entry in @@ -15070,10 +15072,8 @@ private struct SidebarMetadataRows: View { } private var visibleEntries: [SidebarStatusEntry] { - guard !isExpanded, entries.count > SidebarWorkspaceRowDropMetrics.collapsedMetadataEntryLimit else { - return entries - } - return Array(entries.prefix(SidebarWorkspaceRowDropMetrics.collapsedMetadataEntryLimit)) + guard !isExpanded, entries.count > collapsedEntryLimit else { return entries } + return Array(entries.prefix(collapsedEntryLimit)) } private var helpText: String { @@ -15085,7 +15085,7 @@ private struct SidebarMetadataRows: View { } private var shouldShowToggle: Bool { - entries.count > SidebarWorkspaceRowDropMetrics.collapsedMetadataEntryLimit + entries.count > collapsedEntryLimit } } @@ -15190,13 +15190,15 @@ private struct SidebarMetadataEntryRow: View { private struct SidebarMetadataMarkdownBlocks: View { let blocks: [SidebarMetadataBlock] - @Binding var isExpanded: Bool let isActive: Bool let activeForegroundColor: Color let activeSecondaryForegroundColor: Color let fontScale: CGFloat let onFocus: () -> Void + @State private var isExpanded: Bool = false + private let collapsedBlockLimit = 1 + var body: some View { VStack(alignment: .leading, spacing: 3) { ForEach(visibleBlocks, id: \.key) { block in @@ -15225,14 +15227,12 @@ private struct SidebarMetadataMarkdownBlocks: View { } private var visibleBlocks: [SidebarMetadataBlock] { - guard !isExpanded, blocks.count > SidebarWorkspaceRowDropMetrics.collapsedMetadataBlockLimit else { - return blocks - } - return Array(blocks.prefix(SidebarWorkspaceRowDropMetrics.collapsedMetadataBlockLimit)) + guard !isExpanded, blocks.count > collapsedBlockLimit else { return blocks } + return Array(blocks.prefix(collapsedBlockLimit)) } private var shouldShowToggle: Bool { - blocks.count > SidebarWorkspaceRowDropMetrics.collapsedMetadataBlockLimit + blocks.count > collapsedBlockLimit } } @@ -15242,7 +15242,7 @@ private struct SidebarMetadataMarkdownBlockRow: View { let activeForegroundColor: Color let fontScale: CGFloat let onFocus: () -> Void - private static let maxDisplayedLines = SidebarWorkspaceRowDropMetrics.maxMetadataBlockLines + private static let maxDisplayedLines = 12 private static let maxDisplayedCharacters = 4096 var body: some View { diff --git a/Sources/SidebarWorkspaceGroupHeaderMetrics.swift b/Sources/SidebarWorkspaceGroupHeaderMetrics.swift index fc370a412130..ebf0ef9efb02 100644 --- a/Sources/SidebarWorkspaceGroupHeaderMetrics.swift +++ b/Sources/SidebarWorkspaceGroupHeaderMetrics.swift @@ -76,9 +76,4 @@ struct SidebarWorkspaceGroupHeaderMetrics: Equatable { var plusFontSize: CGFloat { Self.basePlusFontSize * fontScale } /// Scaled plus-button frame edge. var plusFrame: CGFloat { max(Self.basePlusFrame, Self.basePlusFrame * fontScale) } - /// Stable drop-hit height for the group header, without reading SwiftUI layout. - var dropTargetHeight: CGFloat { - let contentHeight = max(chevronFrame, iconFrame, plusFrame, nameFontSize + 4) - return max(24 * fontScale, contentHeight + 10) - } } diff --git a/Sources/SidebarWorkspaceGroupHeaderView.swift b/Sources/SidebarWorkspaceGroupHeaderView.swift index 29febd9b4686..7ddb86ac165f 100644 --- a/Sources/SidebarWorkspaceGroupHeaderView.swift +++ b/Sources/SidebarWorkspaceGroupHeaderView.swift @@ -68,9 +68,6 @@ struct SidebarWorkspaceGroupHeaderView: View, Equatable { let isBeingDragged: Bool let topDropIndicatorVisible: Bool let onDragStart: () -> NSItemProvider - /// Factory invoked from `body` with a stable drop-hit height. Closure - /// captures the parent's drag state while this LazyVStack row stays free of - /// layout-driven state writes. let tabDropDelegateFactory: (CGFloat) -> SidebarWorkspaceGroupHeaderDropDelegate let onToggleCollapsed: () -> Void let onFocusAnchor: () -> Void @@ -89,6 +86,7 @@ struct SidebarWorkspaceGroupHeaderView: View, Equatable { let onOpenDocs: () -> Void @State private var rowInteractionState = SidebarWorkspaceRowInteractionState() + @State private var rowHeight: CGFloat = 1 private var metrics: SidebarWorkspaceGroupHeaderMetrics { SidebarWorkspaceGroupHeaderMetrics(fontScale: fontScale) @@ -112,6 +110,18 @@ struct SidebarWorkspaceGroupHeaderView: View, Equatable { return "\(shortcutModifierSymbol)\(shortcutDigit)" } + private var rowHeightProbe: some View { + GeometryReader { proxy in + Color.clear + .onAppear { + rowHeight = max(proxy.size.height, 1) + } + .onChange(of: proxy.size.height) { _, newHeight in + rowHeight = max(newHeight, 1) + } + } + } + var body: some View { HStack(spacing: 4) { Image(systemName: isCollapsed ? "chevron.right" : "chevron.down") @@ -237,6 +247,7 @@ struct SidebarWorkspaceGroupHeaderView: View, Equatable { offsetY: shortcutHintYOffset ) .padding(.horizontal, SidebarWorkspaceListMetrics.rowOuterHorizontalPadding) + .background { rowHeightProbe } .shortcutHintVisibilityAnimation(value: showsShortcutHint) .opacity(isBeingDragged ? 0.6 : 1) .overlay(alignment: .top) { @@ -251,7 +262,7 @@ struct SidebarWorkspaceGroupHeaderView: View, Equatable { } .onDrag(onDragStart) .internalOnlyTabDrag() - .onDrop(of: SidebarTabDragPayload.dropContentTypes, delegate: tabDropDelegateFactory(metrics.dropTargetHeight)) + .onDrop(of: SidebarTabDragPayload.dropContentTypes, delegate: tabDropDelegateFactory(rowHeight)) .contextMenu { Button( String( diff --git a/Sources/SidebarWorkspaceRowDropMetrics.swift b/Sources/SidebarWorkspaceRowDropMetrics.swift deleted file mode 100644 index 9949bed5d5a6..000000000000 --- a/Sources/SidebarWorkspaceRowDropMetrics.swift +++ /dev/null @@ -1,464 +0,0 @@ -import CoreGraphics -import CmuxFoundation -import CmuxSidebar -import Foundation - -struct SidebarWorkspaceRowDropMetrics { - static let minimumTargetHeight: CGFloat = 34 - static let collapsedMetadataEntryLimit = 3 - static let collapsedMetadataBlockLimit = 1 - static let maxWrappedTitleLines = 8 - static let maxDescriptionLines = 12 - private static let maxDescriptionCharacters = 4096 - static let maxMetadataBlockLines = 12 - private static let maxMetadataBlockCharacters = 4096 - private static let defaultEstimatedCharactersPerLine = 42 - private static let topLevelSectionSpacing: CGFloat = 4 - private static let titleGlyphWidthFactor: CGFloat = 0.62 - private static let bodyGlyphWidthFactor: CGFloat = 0.58 - - static func targetHeight( - fontScale: CGFloat, - titleLineCount: Int, - descriptionLineCount: Int, - subtitleLineCount: Int, - hasRemoteStatus: Bool, - metadataEntryCount: Int, - metadataEntryIsExpanded: Bool, - metadataBlockLineCounts: [Int], - hasMetadataBlockToggle: Bool, - hasLog: Bool, - hasProgress: Bool, - branchDirectoryRowCount: Int, - pullRequestRowCount: Int, - hasPorts: Bool - ) -> CGFloat { - let scale = max(fontScale, 0.5) - var height = 16 + CGFloat(max(titleLineCount, 1)) * 16 * scale - var sectionCount = 1 - if descriptionLineCount > 0 { - height += (CGFloat(descriptionLineCount) * 13 + 2) * scale - sectionCount += 1 - } - if subtitleLineCount > 0 { - height += CGFloat(min(max(subtitleLineCount, 1), 2)) * 13 * scale - sectionCount += 1 - } - if hasRemoteStatus { - height += 18 * scale - sectionCount += 1 - } - let metadataEntryHeight = metadataEntriesHeight( - entryCount: metadataEntryCount, - isExpanded: metadataEntryIsExpanded, - scale: scale - ) - if metadataEntryHeight > 0 { - height += metadataEntryHeight - sectionCount += 1 - } - let metadataBlockHeight = metadataBlocksHeight( - lineCounts: metadataBlockLineCounts, - hasToggle: hasMetadataBlockToggle, - scale: scale - ) - if metadataBlockHeight > 0 { - height += metadataBlockHeight - sectionCount += 1 - } - if hasLog { - height += 16 * scale - sectionCount += 1 - } - if hasProgress { - height += 16 * scale - sectionCount += 1 - } - if branchDirectoryRowCount > 0 { - height += (CGFloat(branchDirectoryRowCount) * 13 + CGFloat(max(branchDirectoryRowCount - 1, 0))) * scale - sectionCount += 1 - } - if pullRequestRowCount > 0 { - height += (CGFloat(pullRequestRowCount) * 14 + CGFloat(max(pullRequestRowCount - 1, 0))) * scale - sectionCount += 1 - } - if hasPorts { - height += 16 * scale - sectionCount += 1 - } - height += CGFloat(max(sectionCount - 1, 0)) * topLevelSectionSpacing - return max(minimumTargetHeight, height.rounded(.up)) - } - - static func estimatedMetadataBlockLineCounts( - _ blocks: [SidebarMetadataBlock], - isExpanded: Bool, - textWidth: CGFloat? = nil, - fontScale: CGFloat = 1 - ) -> [Int] { - let visibleBlocks = isExpanded ? blocks : Array(blocks.prefix(collapsedMetadataBlockLimit)) - return visibleBlocks.map { - let visibleText = visibleMetadataBlockText($0.markdown) - return estimatedLineCount( - visibleText, - maxLines: maxMetadataBlockLines, - textWidth: textWidth, - fontSize: 10 * max(fontScale, 0.5), - glyphWidthFactor: bodyGlyphWidthFactor - ) - } - } - - static func estimatedTitleLineCount( - _ title: String, - wraps: Bool, - textWidth: CGFloat? = nil, - fontScale: CGFloat = 1 - ) -> Int { - wraps - ? estimatedLineCount( - title, - maxLines: maxWrappedTitleLines, - textWidth: textWidth, - fontSize: 12.5 * max(fontScale, 0.5), - glyphWidthFactor: titleGlyphWidthFactor - ) - : 1 - } - - static func estimatedDescriptionLineCount( - _ description: String?, - textWidth: CGFloat? = nil, - fontScale: CGFloat = 1 - ) -> Int { - guard let description else { return 0 } - let visibleDescription = visibleWorkspaceDescriptionText(description) - return estimatedLineCount( - visibleDescription, - maxLines: maxDescriptionLines, - textWidth: textWidth, - fontSize: 10.5 * max(fontScale, 0.5), - glyphWidthFactor: bodyGlyphWidthFactor - ) - } - - static func estimatedSubtitleLineCount( - _ subtitle: String?, - textWidth: CGFloat? = nil, - fontScale: CGFloat = 1 - ) -> Int { - guard let subtitle else { return 0 } - return estimatedLineCount( - subtitle, - maxLines: 2, - textWidth: textWidth, - fontSize: 10 * max(fontScale, 0.5), - glyphWidthFactor: bodyGlyphWidthFactor - ) - } - - private static func metadataEntriesHeight( - entryCount: Int, - isExpanded: Bool, - scale: CGFloat - ) -> CGFloat { - guard entryCount > 0 else { return 0 } - let visibleCount = isExpanded ? entryCount : min(entryCount, collapsedMetadataEntryLimit) - let toggleCount = entryCount > collapsedMetadataEntryLimit ? 1 : 0 - let rowCount = visibleCount + toggleCount - return (CGFloat(rowCount) * 13 + CGFloat(max(rowCount - 1, 0)) * 2) * scale - } - - private static func metadataBlocksHeight( - lineCounts: [Int], - hasToggle: Bool, - scale: CGFloat - ) -> CGFloat { - guard !lineCounts.isEmpty || hasToggle else { return 0 } - let visibleLineCount = lineCounts.reduce(0) { $0 + max($1, 1) } - let toggleCount = hasToggle ? 1 : 0 - let blockSpacingCount = max(lineCounts.count + toggleCount - 1, 0) - return (CGFloat(visibleLineCount) * 13 + CGFloat(toggleCount) * 13 + CGFloat(blockSpacingCount) * 3) * scale - } - - private static func visibleWorkspaceDescriptionText(_ markdown: String) -> String { - let displayMarkdown = boundedDisplayString( - markdown, - maxDisplayedLines: maxDescriptionLines, - maxDisplayedCharacters: maxDescriptionCharacters - ) - return markdownVisibleTextEstimate(displayMarkdown) - } - - private static func visibleMetadataBlockText(_ markdown: String) -> String { - let displayMarkdown = boundedDisplayString( - markdown, - maxDisplayedLines: maxMetadataBlockLines, - maxDisplayedCharacters: maxMetadataBlockCharacters - ) - return markdownVisibleTextEstimate(displayMarkdown) - } - - private static func boundedDisplayString( - _ text: String, - maxDisplayedLines: Int, - maxDisplayedCharacters: Int - ) -> String { - var result = "" - result.reserveCapacity(maxDisplayedCharacters) - var lineCount = 1 - var characterCount = 0 - var truncated = false - - for character in text { - if characterCount >= maxDisplayedCharacters { - truncated = true - break - } - if character == "\n" { - if lineCount >= maxDisplayedLines { - truncated = true - break - } - lineCount += 1 - } - result.append(character) - characterCount += 1 - } - - guard truncated else { return text } - let trimmed = result.trimmingCharacters(in: .whitespacesAndNewlines) - return trimmed.isEmpty ? "..." : trimmed + "..." - } - - private static func markdownVisibleTextEstimate(_ markdown: String) -> String { - var result = "" - result.reserveCapacity(markdown.count) - var index = markdown.startIndex - - while index < markdown.endIndex { - if markdown[index] == "!", - let next = markdown.index(index, offsetBy: 1, limitedBy: markdown.endIndex), - next < markdown.endIndex, - markdown[next] == "[", - let link = markdownLinkText(in: markdown, labelStart: markdown.index(after: next)) { - result += link.label - index = link.end - continue - } - - if markdown[index] == "[", - let link = markdownLinkText(in: markdown, labelStart: markdown.index(after: index)) { - result += link.label - index = link.end - continue - } - - result.append(markdown[index]) - index = markdown.index(after: index) - } - - return result - } - - private static func markdownLinkText( - in markdown: String, - labelStart: String.Index - ) -> (label: String, end: String.Index)? { - guard let labelEnd = markdown[labelStart...].firstIndex(of: "]") else { - return nil - } - let destinationStart = markdown.index(after: labelEnd) - guard destinationStart < markdown.endIndex, - markdown[destinationStart] == "(", - let destinationEnd = markdown[destinationStart...].firstIndex(of: ")") else { - return nil - } - let end = markdown.index(after: destinationEnd) - return (String(markdown[labelStart.. CGFloat { - max( - 1, - sidebarWidth - 2 * ( - SidebarWorkspaceListMetrics.rowOuterHorizontalPadding + - SidebarWorkspaceListMetrics.rowContentHorizontalPadding - ) - ) - } - - static func titleTextWidth( - sidebarWidth: CGFloat, - unreadCount: Int, - isPinned: Bool, - canCloseWorkspace: Bool, - fontScale: CGFloat - ) -> CGFloat { - let scale = max(fontScale, 0.5) - var occupiedWidth: CGFloat = 0 - var itemCount = 1 - - func addAccessory(width: CGFloat) { - occupiedWidth += width - itemCount += 1 - } - - if unreadCount > 0 { - addAccessory(width: 16 * scale) - } - if isPinned { - addAccessory(width: 10 * scale) - } - if canCloseWorkspace { - addAccessory(width: max(SidebarTrailingAccessoryWidthPolicy().closeButtonWidth, 16 * scale)) - } - - let spacing = CGFloat(max(itemCount - 1, 0)) * 8 - return max(1, rowContentWidth(sidebarWidth: sidebarWidth) - occupiedWidth - spacing) - } - - private static func estimatedLineCount( - _ text: String, - maxLines: Int = maxMetadataBlockLines, - textWidth: CGFloat? = nil, - fontSize: CGFloat = 10, - glyphWidthFactor: CGFloat = bodyGlyphWidthFactor - ) -> Int { - let charactersPerLine = estimatedCharactersPerLine( - textWidth: textWidth, - fontSize: fontSize, - glyphWidthFactor: glyphWidthFactor - ) - let boundedText = String(text.prefix(charactersPerLine * maxLines)) - let lines = boundedText.components(separatedBy: .newlines) - let lineCount = lines.reduce(0) { count, line in - let characterCount = line.trimmingCharacters(in: .whitespacesAndNewlines).count - return count + max(1, Int(ceil(Double(characterCount) / Double(charactersPerLine)))) - } - return min(max(lineCount, 1), maxLines) - } - - private static func estimatedCharactersPerLine( - textWidth: CGFloat?, - fontSize: CGFloat, - glyphWidthFactor: CGFloat - ) -> Int { - guard let textWidth, textWidth.isFinite, textWidth > 0 else { - return defaultEstimatedCharactersPerLine - } - let glyphWidth = max(1, fontSize * glyphWidthFactor) - return max(1, Int(floor(textWidth / glyphWidth))) - } - - private static func branchDirectoryRowCount( - snapshot: SidebarWorkspaceSnapshotBuilder.Snapshot, - settings: SidebarTabItemSettingsSnapshot - ) -> Int { - guard settings.visibleAuxiliaryDetails.showsBranchDirectory else { return 0 } - if settings.usesVerticalBranchLayout { - return snapshot.branchDirectoryLines.reduce(0) { count, line in - if settings.stacksBranchAndDirectory { - let branchCount = line.branch == nil ? 0 : 1 - let directoryCount = line.directoryCandidates.isEmpty ? 0 : 1 - return count + max(branchCount + directoryCount, 1) - } - return count + 1 - } - } - if settings.stacksBranchAndDirectory, - (snapshot.compactGitBranchSummaryText != nil || !snapshot.compactDirectoryCandidates.isEmpty) { - let branchCount = snapshot.compactGitBranchSummaryText == nil ? 0 : 1 - let directoryCount = snapshot.compactDirectoryCandidates.isEmpty ? 0 : 1 - return max(branchCount + directoryCount, 1) - } - if !snapshot.compactBranchDirectoryCandidates.isEmpty { - return 1 - } - return 0 - } - - static func targetHeight( - snapshot: SidebarWorkspaceSnapshotBuilder.Snapshot, - settings: SidebarTabItemSettingsSnapshot, - effectiveSubtitle: String?, - metadataEntryIsExpanded: Bool, - metadataBlocksAreExpanded: Bool, - sidebarWidth: CGFloat = CGFloat(SessionPersistencePolicy.defaultSidebarWidth), - unreadCount: Int = 0, - canCloseWorkspace: Bool = true - ) -> CGFloat { - let visibleDetails = settings.visibleAuxiliaryDetails - let metadataEntryCount = visibleDetails.showsMetadata ? snapshot.metadataEntries.count : 0 - let scale = max(settings.sidebarFontScale, 0.5) - let bodyTextWidth = rowContentWidth(sidebarWidth: sidebarWidth) - let resolvedTitleTextWidth = titleTextWidth( - sidebarWidth: sidebarWidth, - unreadCount: unreadCount, - isPinned: snapshot.isPinned, - canCloseWorkspace: canCloseWorkspace, - fontScale: scale - ) - let metadataBlockLineCounts = visibleDetails.showsMetadata - ? estimatedMetadataBlockLineCounts( - snapshot.metadataBlocks, - isExpanded: metadataBlocksAreExpanded, - textWidth: bodyTextWidth, - fontScale: scale - ) - : [] - let hasMetadataBlockToggle = visibleDetails.showsMetadata && - snapshot.metadataBlocks.count > collapsedMetadataBlockLimit - return targetHeight( - fontScale: settings.sidebarFontScale, - titleLineCount: estimatedTitleLineCount( - snapshot.title, - wraps: settings.wrapsWorkspaceTitles, - textWidth: resolvedTitleTextWidth, - fontScale: scale - ), - descriptionLineCount: estimatedDescriptionLineCount( - snapshot.customDescription, - textWidth: bodyTextWidth, - fontScale: scale - ), - subtitleLineCount: estimatedSubtitleLineCount( - effectiveSubtitle, - textWidth: bodyTextWidth, - fontScale: scale - ), - hasRemoteStatus: !settings.hidesAllDetails && settings.showsSSH && snapshot.remoteWorkspaceSidebarText != nil, - metadataEntryCount: metadataEntryCount, - metadataEntryIsExpanded: metadataEntryIsExpanded, - metadataBlockLineCounts: metadataBlockLineCounts, - hasMetadataBlockToggle: hasMetadataBlockToggle, - hasLog: visibleDetails.showsLog && snapshot.latestLog != nil, - hasProgress: visibleDetails.showsProgress && snapshot.progress != nil, - branchDirectoryRowCount: branchDirectoryRowCount(snapshot: snapshot, settings: settings), - pullRequestRowCount: visibleDetails.showsPullRequests ? snapshot.pullRequestRows.count : 0, - hasPorts: visibleDetails.showsPorts && !snapshot.listeningPorts.isEmpty - ) - } - - static func dropTargetHeight( - snapshot: SidebarWorkspaceSnapshotBuilder.Snapshot, - settings: SidebarTabItemSettingsSnapshot, - effectiveSubtitle: String?, - metadataEntryIsExpanded: Bool, - metadataBlocksAreExpanded: Bool, - sidebarWidth: CGFloat = CGFloat(SessionPersistencePolicy.defaultSidebarWidth), - unreadCount: Int = 0, - canCloseWorkspace: Bool = true - ) -> CGFloat { - return targetHeight( - snapshot: snapshot, - settings: settings, - effectiveSubtitle: effectiveSubtitle, - metadataEntryIsExpanded: metadataEntryIsExpanded, - metadataBlocksAreExpanded: metadataBlocksAreExpanded, - sidebarWidth: sidebarWidth, - unreadCount: unreadCount, - canCloseWorkspace: canCloseWorkspace - ) - } -} diff --git a/cmux.xcodeproj/project.pbxproj b/cmux.xcodeproj/project.pbxproj index b033079134c5..b78af9532481 100644 --- a/cmux.xcodeproj/project.pbxproj +++ b/cmux.xcodeproj/project.pbxproj @@ -815,7 +815,6 @@ F57072635F25EBCA741E125D /* SidebarState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1614EAD3CCF70A177A51BD1 /* SidebarState.swift */; }; D7AB34300000000000000105 /* SidebarTabDropIndicatorPredicateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7AB34300000000000000106 /* SidebarTabDropIndicatorPredicateTests.swift */; }; CA39C0304FE351A21C372429 /* SidebarWidthPolicyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EE0171AF1F49F7547191CEE5 /* SidebarWidthPolicyTests.swift */; }; - C9A57403C9A57403C9A57403 /* SidebarWorkspaceDropMetricsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9A57404C9A57404C9A57404 /* SidebarWorkspaceDropMetricsTests.swift */; }; D7AB34300000000000000005 /* SidebarWorkspaceDropPlannerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7AB34300000000000000006 /* SidebarWorkspaceDropPlannerTests.swift */; }; C9A57103C9A57103C9A57103 /* SidebarWorkspaceGroupConfigOpener.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9A57104C9A57104C9A57104 /* SidebarWorkspaceGroupConfigOpener.swift */; }; C135190000000000000000B1 /* SidebarWorkspaceGroupConfigOpenerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C135190000000000000000B2 /* SidebarWorkspaceGroupConfigOpenerTests.swift */; }; @@ -827,7 +826,6 @@ C9A57101C9A57101C9A57101 /* SidebarWorkspaceGroupHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9A57102C9A57102C9A57102 /* SidebarWorkspaceGroupHeaderView.swift */; }; C9A57109C9A57109C9A57109 /* SidebarWorkspaceGroupingMetrics.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9A5710AC9A5710AC9A5710A /* SidebarWorkspaceGroupingMetrics.swift */; }; C9A57201C9A57201C9A57201 /* SidebarWorkspaceRenderItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9A57202C9A57202C9A57202 /* SidebarWorkspaceRenderItem.swift */; }; - C9A57401C9A57401C9A57401 /* SidebarWorkspaceRowDropMetrics.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9A57402C9A57402C9A57402 /* SidebarWorkspaceRowDropMetrics.swift */; }; D35450010000000000000001 /* SidebarWorkspaceRowHoverTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = D35450020000000000000002 /* SidebarWorkspaceRowHoverTracker.swift */; }; C9A57505C9A57505C9A57505 /* SidebarWorkspaceScrollLayoutTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9A57506C9A57506C9A57506 /* SidebarWorkspaceScrollLayoutTests.swift */; }; C0DE56010000000000000001 /* SidebarWorkspaceSelectionAnchorPolicyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0DE56010000000000000002 /* SidebarWorkspaceSelectionAnchorPolicyTests.swift */; }; @@ -1879,7 +1877,6 @@ D1614EAD3CCF70A177A51BD1 /* SidebarState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Sidebar/SidebarState.swift; sourceTree = ""; }; D7AB34300000000000000106 /* SidebarTabDropIndicatorPredicateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarTabDropIndicatorPredicateTests.swift; sourceTree = ""; }; EE0171AF1F49F7547191CEE5 /* SidebarWidthPolicyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarWidthPolicyTests.swift; sourceTree = ""; }; - C9A57404C9A57404C9A57404 /* SidebarWorkspaceDropMetricsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarWorkspaceDropMetricsTests.swift; sourceTree = ""; }; D7AB34300000000000000006 /* SidebarWorkspaceDropPlannerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarWorkspaceDropPlannerTests.swift; sourceTree = ""; }; C9A57104C9A57104C9A57104 /* SidebarWorkspaceGroupConfigOpener.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarWorkspaceGroupConfigOpener.swift; sourceTree = ""; }; C135190000000000000000B2 /* SidebarWorkspaceGroupConfigOpenerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarWorkspaceGroupConfigOpenerTests.swift; sourceTree = ""; }; @@ -1891,7 +1888,6 @@ C9A57102C9A57102C9A57102 /* SidebarWorkspaceGroupHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarWorkspaceGroupHeaderView.swift; sourceTree = ""; }; C9A5710AC9A5710AC9A5710A /* SidebarWorkspaceGroupingMetrics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarWorkspaceGroupingMetrics.swift; sourceTree = ""; }; C9A57202C9A57202C9A57202 /* SidebarWorkspaceRenderItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarWorkspaceRenderItem.swift; sourceTree = ""; }; - C9A57402C9A57402C9A57402 /* SidebarWorkspaceRowDropMetrics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarWorkspaceRowDropMetrics.swift; sourceTree = ""; }; D35450020000000000000002 /* SidebarWorkspaceRowHoverTracker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Sidebar/SidebarWorkspaceRowHoverTracker.swift; sourceTree = ""; }; C9A57506C9A57506C9A57506 /* SidebarWorkspaceScrollLayoutTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarWorkspaceScrollLayoutTests.swift; sourceTree = ""; }; C0DE56010000000000000002 /* SidebarWorkspaceSelectionAnchorPolicyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarWorkspaceSelectionAnchorPolicyTests.swift; sourceTree = ""; }; @@ -2451,7 +2447,6 @@ C9A5710AC9A5710AC9A5710A /* SidebarWorkspaceGroupingMetrics.swift */, D5037010000000000000002 /* RenderableSystemSymbol.swift */, C9A57202C9A57202C9A57202 /* SidebarWorkspaceRenderItem.swift */, - C9A57402C9A57402C9A57402 /* SidebarWorkspaceRowDropMetrics.swift */, C0DE5F210000000000000002 /* SidebarMetadataMarkdownRenderer.swift */, C9A5720CC9A5720CC9A5720C /* TabItemView+WorkspaceGroups.swift */, C9A5720AC9A5720AC9A5720A /* VerticalTabsSidebar+WorkspaceGroups.swift */, @@ -3250,7 +3245,6 @@ C7A509000000000000000001 /* CmuxTopSnapshotScopeTests.swift */, C7A5090000000000000005A1 /* CmuxTopSnapshotScopeCacheTests.swift */, C7A50C000000000000000001 /* CmuxTopProcessCPUTests.swift */, - C9A57404C9A57404C9A57404 /* SidebarWorkspaceDropMetricsTests.swift */, D7AB34300000000000000006 /* SidebarWorkspaceDropPlannerTests.swift */, D7AB34300000000000000106 /* SidebarTabDropIndicatorPredicateTests.swift */, A47E00010000000000000002 /* AuthEnvironmentTests.swift */, @@ -4178,7 +4172,6 @@ C9A57101C9A57101C9A57101 /* SidebarWorkspaceGroupHeaderView.swift in Sources */, C9A57109C9A57109C9A57109 /* SidebarWorkspaceGroupingMetrics.swift in Sources */, C9A57201C9A57201C9A57201 /* SidebarWorkspaceRenderItem.swift in Sources */, - C9A57401C9A57401C9A57401 /* SidebarWorkspaceRowDropMetrics.swift in Sources */, D35450010000000000000001 /* SidebarWorkspaceRowHoverTracker.swift in Sources */, 988C6A036BA56EA5759A95A0 /* SidebarWorkspaceSnapshotRefreshPolicy.swift in Sources */, A5001226 /* SocketControlMode+Display.swift in Sources */, @@ -4658,7 +4651,6 @@ C9A57513C9A57513C9A57513 /* SidebarScrollViewConfiguratorTests.swift in Sources */, D7AB34300000000000000105 /* SidebarTabDropIndicatorPredicateTests.swift in Sources */, CA39C0304FE351A21C372429 /* SidebarWidthPolicyTests.swift in Sources */, - C9A57403C9A57403C9A57403 /* SidebarWorkspaceDropMetricsTests.swift in Sources */, D7AB34300000000000000005 /* SidebarWorkspaceDropPlannerTests.swift in Sources */, C135190000000000000000B1 /* SidebarWorkspaceGroupConfigOpenerTests.swift in Sources */, C9A57303C9A57303C9A57303 /* SidebarWorkspaceGroupHeaderMetricsTests.swift in Sources */, diff --git a/cmuxTests/SidebarWorkspaceDropMetricsTests.swift b/cmuxTests/SidebarWorkspaceDropMetricsTests.swift deleted file mode 100644 index 88e578b55a20..000000000000 --- a/cmuxTests/SidebarWorkspaceDropMetricsTests.swift +++ /dev/null @@ -1,279 +0,0 @@ -import CoreGraphics -import CmuxSidebar -import Foundation -import Testing - -#if canImport(cmux_DEV) -@testable import cmux_DEV -#elseif canImport(cmux) -@testable import cmux -#endif - -@Suite struct SidebarWorkspaceDropMetricsTests { - @Test func workspaceGroupHeaderDropTargetHeightScalesWithoutLayoutMeasurement() { - #expect(SidebarWorkspaceGroupHeaderMetrics(fontScale: 1).dropTargetHeight == 26) - #expect(SidebarWorkspaceGroupHeaderMetrics(fontScale: 2).dropTargetHeight == 48) - } - - @Test func workspaceRowDropTargetHeightScalesWithContentWithoutLayoutMeasurement() { - let base = workspaceRowHeight() - let rich = workspaceRowHeight( - titleLineCount: 2, - descriptionLineCount: 2, - subtitleLineCount: 1, - hasRemoteStatus: true, - metadataEntryCount: 4, - metadataEntryIsExpanded: true, - metadataBlockLineCounts: [2, 3], - hasMetadataBlockToggle: true, - hasLog: true, - hasProgress: true, - branchDirectoryRowCount: 2, - pullRequestRowCount: 2, - hasPorts: true - ) - let scaled = workspaceRowHeight(fontScale: 1.5) - - #expect(base == 34) - #expect(rich > base) - #expect(scaled > base) - } - - @Test func workspaceRowDropTargetHeightTracksExpandedMetadataRows() { - let collapsed = workspaceRowHeight(metadataEntryCount: 6, metadataEntryIsExpanded: false) - let expanded = workspaceRowHeight(metadataEntryCount: 6, metadataEntryIsExpanded: true) - - #expect(expanded > collapsed) - } - - @Test func workspaceRowDropTargetHeightTracksExpandedMetadataBlocks() { - let collapsed = workspaceRowHeight( - metadataBlockLineCounts: [1], - hasMetadataBlockToggle: true - ) - let expanded = workspaceRowHeight( - metadataBlockLineCounts: [1, 4, 2], - hasMetadataBlockToggle: true - ) - - #expect(expanded > collapsed) - } - - @Test func workspaceRowDropTargetHeightIncludesTopLevelSectionSpacing() { - let withDescription = workspaceRowHeight(descriptionLineCount: 1) - let withDescriptionAndSubtitle = workspaceRowHeight( - descriptionLineCount: 1, - subtitleLineCount: 1 - ) - - #expect(workspaceRowHeight() == 34) - #expect(withDescription == 51) - #expect(withDescriptionAndSubtitle == 68) - } - - @Test func workspaceRowDropTargetHeightTracksSubtitleLineCount() { - let oneLineSubtitle = workspaceRowHeight(subtitleLineCount: 1) - let twoLineSubtitle = workspaceRowHeight(subtitleLineCount: 2) - - #expect(oneLineSubtitle == 49) - #expect(twoLineSubtitle == 62) - } - - @Test func metadataBlockLineEstimationOnlyScansVisibleCollapsedBlock() { - let blocks = [ - SidebarMetadataBlock(key: "visible", markdown: "one", priority: 0, timestamp: Date()), - SidebarMetadataBlock( - key: "hidden", - markdown: String(repeating: "hidden\n", count: 100), - priority: 0, - timestamp: Date() - ), - ] - - #expect(SidebarWorkspaceRowDropMetrics.estimatedMetadataBlockLineCounts(blocks, isExpanded: false) == [1]) - #expect(SidebarWorkspaceRowDropMetrics.estimatedMetadataBlockLineCounts(blocks, isExpanded: true) == [1, 12]) - } - - @Test func metadataBlockLineEstimationUsesRenderedMarkdownText() { - let longURL = "https://example.com/" + String(repeating: "hidden-url-segment/", count: 24) - let block = SidebarMetadataBlock( - key: "link", - markdown: "[short](\(longURL))", - priority: 0, - timestamp: Date() - ) - - let lineCounts = SidebarWorkspaceRowDropMetrics.estimatedMetadataBlockLineCounts( - [block], - isExpanded: false, - textWidth: 48 - ) - - #expect(lineCounts == [1]) - } - - @Test func workspaceRowDropTargetHeightUsesLineAwareTitleAndDescriptionEstimates() { - let short = workspaceRowHeight(titleLineCount: 1, descriptionLineCount: 1) - let tall = workspaceRowHeight(titleLineCount: 4, descriptionLineCount: 5) - - #expect(tall > short) - } - - @Test func workspaceDescriptionLineEstimationUsesRenderedMarkdownText() { - let longURL = "https://example.com/" + String(repeating: "hidden-url-segment/", count: 24) - let lineCount = SidebarWorkspaceRowDropMetrics.estimatedDescriptionLineCount( - "[short](\(longURL))", - textWidth: 48 - ) - - #expect(lineCount == 1) - } - - @Test func workspaceRowDropTargetHeightKeepsPointerEdgeMetricsForWrappedAndRichRows() { - let snapshot = workspaceSnapshot( - title: String(repeating: "Long workspace title ", count: 12), - customDescription: "First description line\nSecond description line", - metadataEntries: [ - SidebarStatusEntry(key: "state", value: "running"), - SidebarStatusEntry(key: "phase", value: "building"), - SidebarStatusEntry(key: "owner", value: "agent") - ], - metadataBlocks: [ - SidebarMetadataBlock( - key: "notes", - markdown: "Line one\nLine two\nLine three", - priority: 0, - timestamp: Date() - ) - ] - ) - - let wideHeight = SidebarWorkspaceRowDropMetrics.dropTargetHeight( - snapshot: snapshot, - settings: settings(wrapsWorkspaceTitles: true), - effectiveSubtitle: "Recent update", - metadataEntryIsExpanded: false, - metadataBlocksAreExpanded: false, - sidebarWidth: 600, - unreadCount: 0, - canCloseWorkspace: true - ) - let narrowHeight = SidebarWorkspaceRowDropMetrics.dropTargetHeight( - snapshot: snapshot, - settings: settings(wrapsWorkspaceTitles: true), - effectiveSubtitle: "Recent update", - metadataEntryIsExpanded: false, - metadataBlocksAreExpanded: false, - sidebarWidth: 216, - unreadCount: 12, - canCloseWorkspace: true - ) - let groupedNarrowHeight = SidebarWorkspaceRowDropMetrics.dropTargetHeight( - snapshot: snapshot, - settings: settings(wrapsWorkspaceTitles: true), - effectiveSubtitle: "Recent update", - metadataEntryIsExpanded: false, - metadataBlocksAreExpanded: false, - sidebarWidth: 216 - SidebarWorkspaceGroupingMetrics.memberIndent, - unreadCount: 12, - canCloseWorkspace: true - ) - - #expect(wideHeight > workspaceRowHeight()) - #expect(narrowHeight > wideHeight) - #expect(groupedNarrowHeight >= narrowHeight) - } - - private func workspaceRowHeight( - fontScale: CGFloat = 1, - titleLineCount: Int = 1, - descriptionLineCount: Int = 0, - subtitleLineCount: Int = 0, - hasRemoteStatus: Bool = false, - metadataEntryCount: Int = 0, - metadataEntryIsExpanded: Bool = false, - metadataBlockLineCounts: [Int] = [], - hasMetadataBlockToggle: Bool = false, - hasLog: Bool = false, - hasProgress: Bool = false, - branchDirectoryRowCount: Int = 0, - pullRequestRowCount: Int = 0, - hasPorts: Bool = false - ) -> CGFloat { - SidebarWorkspaceRowDropMetrics.targetHeight( - fontScale: fontScale, - titleLineCount: titleLineCount, - descriptionLineCount: descriptionLineCount, - subtitleLineCount: subtitleLineCount, - hasRemoteStatus: hasRemoteStatus, - metadataEntryCount: metadataEntryCount, - metadataEntryIsExpanded: metadataEntryIsExpanded, - metadataBlockLineCounts: metadataBlockLineCounts, - hasMetadataBlockToggle: hasMetadataBlockToggle, - hasLog: hasLog, - hasProgress: hasProgress, - branchDirectoryRowCount: branchDirectoryRowCount, - pullRequestRowCount: pullRequestRowCount, - hasPorts: hasPorts - ) - } - - private func settings(wrapsWorkspaceTitles: Bool = false) -> SidebarTabItemSettingsSnapshot { - let suiteName = "SidebarWorkspaceDropMetricsTests.\(UUID().uuidString)" - guard let defaults = UserDefaults(suiteName: suiteName) else { - fatalError("Unable to create test UserDefaults suite") - } - defaults.set(wrapsWorkspaceTitles, forKey: SidebarWorkspaceTitleWrapSettings.key) - let settings = SidebarTabItemSettingsSnapshot(defaults: defaults) - defaults.removePersistentDomain(forName: suiteName) - return settings - } - - private func workspaceSnapshot( - title: String = "workspace", - customDescription: String? = nil, - metadataEntries: [SidebarStatusEntry] = [], - metadataBlocks: [SidebarMetadataBlock] = [] - ) -> SidebarWorkspaceSnapshotBuilder.Snapshot { - let visibleAuxiliaryDetails = SidebarWorkspaceAuxiliaryDetailVisibility( - showsMetadata: true, - showsLog: true, - showsProgress: true, - showsBranchDirectory: true, - showsPullRequests: true, - showsPorts: true - ) - return SidebarWorkspaceSnapshotBuilder.Snapshot( - presentationKey: SidebarWorkspaceSnapshotBuilder.PresentationKey( - showsWorkspaceDescription: true, - usesVerticalBranchLayout: true, - showsGitBranch: true, - usesViewportAwarePath: false, - visibleAuxiliaryDetails: visibleAuxiliaryDetails - ), - title: title, - customDescription: customDescription, - isPinned: false, - customColorHex: nil, - remoteWorkspaceSidebarText: nil, - remoteConnectionStatusText: "", - remoteStateHelpText: "", - showsRemoteReconnectAffordance: false, - copyableSidebarSSHError: nil, - latestConversationMessage: nil, - metadataEntries: metadataEntries, - metadataBlocks: metadataBlocks, - latestLog: nil, - progress: nil, - compactGitBranchSummaryText: nil, - compactDirectoryCandidates: [], - compactBranchDirectoryCandidates: [], - branchDirectoryLines: [], - branchLinesContainBranch: false, - pullRequestRows: [], - listeningPorts: [], - finderDirectoryPath: nil, - mediaActivity: BrowserMediaActivity() - ) - } -} From e629d1b0570a424924ed56a96c8271af3c91d539 Mon Sep 17 00:00:00 2001 From: austinpower1258 Date: Mon, 22 Jun 2026 17:38:17 -0700 Subject: [PATCH 2/2] Use modern sidebar row height onChange signature --- Sources/ContentView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/ContentView.swift b/Sources/ContentView.swift index 5f624f7a7276..f9b82209dda3 100644 --- a/Sources/ContentView.swift +++ b/Sources/ContentView.swift @@ -13258,7 +13258,7 @@ struct TabItemView: View, Equatable { .onAppear { rowHeight = max(proxy.size.height, 1) } - .onChange(of: proxy.size.height) { newHeight in + .onChange(of: proxy.size.height) { _, newHeight in rowHeight = max(newHeight, 1) } }