From c6d3bd3458e03ef6a8d300fa2278a7fff628db68 Mon Sep 17 00:00:00 2001 From: austinpower1258 Date: Thu, 4 Jun 2026 16:40:10 -0700 Subject: [PATCH 1/3] Add failing test for sidebar group header font scaling (#5398) Extract the workspace group/folder header's font sizes, frames, and badge padding into a pure SidebarWorkspaceGroupHeaderMetrics helper and add a behavior-level test asserting the metrics scale with the sidebar font scale. This commit captures only the current (unscaled) behavior plus the failing test: the header still ignores the sidebar font scale, so the enlarged-scale assertions go red. The fix follows in the next commit. Co-Authored-By: Claude Opus 4.8 --- .../SidebarWorkspaceGroupHeaderMetrics.swift | 76 +++++++++++++++++++ cmux.xcodeproj/project.pbxproj | 8 ++ ...ebarWorkspaceGroupHeaderMetricsTests.swift | 53 +++++++++++++ 3 files changed, 137 insertions(+) create mode 100644 Sources/SidebarWorkspaceGroupHeaderMetrics.swift create mode 100644 cmuxTests/SidebarWorkspaceGroupHeaderMetricsTests.swift diff --git a/Sources/SidebarWorkspaceGroupHeaderMetrics.swift b/Sources/SidebarWorkspaceGroupHeaderMetrics.swift new file mode 100644 index 000000000000..765d5cc91c73 --- /dev/null +++ b/Sources/SidebarWorkspaceGroupHeaderMetrics.swift @@ -0,0 +1,76 @@ +import CoreGraphics + +/// Font sizes, icon/control frames, and badge padding for +/// ``SidebarWorkspaceGroupHeaderView``, derived from the sidebar font scale. +/// +/// The collapsible workspace group/folder header must grow proportionally with +/// the configurable sidebar font size, just like the workspace rows below it. +/// `TabItemView` already scales its subviews by `settings.sidebarFontScale` +/// (see ``SidebarTabItemFontScale``); this type is the single place that +/// applies the same scale to the group header so the two grow at the same rate +/// from one scaling path. +/// +/// The `base*` constants are the design sizes at the default sidebar font size, +/// where ``SidebarTabItemFontScale/scale(for:)`` returns `1.0`. Every metric is +/// the corresponding base value multiplied by ``fontScale``. +/// +/// ```swift +/// let metrics = SidebarWorkspaceGroupHeaderMetrics(fontScale: settings.sidebarFontScale) +/// Image(systemName: "chevron.down") +/// .font(.system(size: metrics.chevronFontSize, weight: .semibold)) +/// .frame(width: metrics.chevronFrame, height: metrics.chevronFrame) +/// ``` +struct SidebarWorkspaceGroupHeaderMetrics: Equatable { + /// The sidebar font scale; `1.0` at the default sidebar font size and + /// proportionally larger as the configured `sidebar-font-size` grows. + let fontScale: CGFloat + + /// Creates header metrics for the given sidebar font scale. + /// - Parameter fontScale: The scale from ``SidebarTabItemFontScale/scale(for:)``; + /// `1.0` at the default sidebar font size. + init(fontScale: CGFloat) { + self.fontScale = fontScale + } + + /// Chevron glyph point size at the default sidebar font size. + static let baseChevronFontSize: CGFloat = 9 + /// Chevron tap-target frame edge at the default sidebar font size. + static let baseChevronFrame: CGFloat = 14 + /// Folder/group icon point size at the default sidebar font size. + static let baseIconFontSize: CGFloat = 11 + /// Folder/group icon frame edge at the default sidebar font size. + static let baseIconFrame: CGFloat = 14 + /// Group name point size at the default sidebar font size. + static let baseNameFontSize: CGFloat = 11 + /// Unread badge point size at the default sidebar font size. + static let baseUnreadFontSize: CGFloat = 10 + /// Unread badge horizontal padding at the default sidebar font size. + static let baseUnreadHorizontalPadding: CGFloat = 5 + /// Unread badge vertical padding at the default sidebar font size. + static let baseUnreadVerticalPadding: CGFloat = 1 + /// Plus-button glyph point size at the default sidebar font size. + static let basePlusFontSize: CGFloat = 11 + /// Plus-button frame edge at the default sidebar font size. + static let basePlusFrame: CGFloat = 18 + + /// Scaled chevron glyph point size. + var chevronFontSize: CGFloat { Self.baseChevronFontSize } + /// Scaled chevron tap-target frame edge. + var chevronFrame: CGFloat { Self.baseChevronFrame } + /// Scaled folder/group icon point size. + var iconFontSize: CGFloat { Self.baseIconFontSize } + /// Scaled folder/group icon frame edge. + var iconFrame: CGFloat { Self.baseIconFrame } + /// Scaled group name point size. + var nameFontSize: CGFloat { Self.baseNameFontSize } + /// Scaled unread badge point size. + var unreadFontSize: CGFloat { Self.baseUnreadFontSize } + /// Scaled unread badge horizontal padding. + var unreadHorizontalPadding: CGFloat { Self.baseUnreadHorizontalPadding } + /// Scaled unread badge vertical padding. + var unreadVerticalPadding: CGFloat { Self.baseUnreadVerticalPadding } + /// Scaled plus-button glyph point size. + var plusFontSize: CGFloat { Self.basePlusFontSize } + /// Scaled plus-button frame edge. + var plusFrame: CGFloat { Self.basePlusFrame } +} diff --git a/cmux.xcodeproj/project.pbxproj b/cmux.xcodeproj/project.pbxproj index 34e3c9c5f741..9939e17a59e5 100644 --- a/cmux.xcodeproj/project.pbxproj +++ b/cmux.xcodeproj/project.pbxproj @@ -483,6 +483,8 @@ C135190000000000000000B1 /* SidebarWorkspaceGroupConfigOpenerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C135190000000000000000B2 /* SidebarWorkspaceGroupConfigOpenerTests.swift */; }; C9A57105C9A57105C9A57105 /* SidebarWorkspaceGroupContextMenuRunner.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9A57106C9A57106C9A57106 /* SidebarWorkspaceGroupContextMenuRunner.swift */; }; C9A57107C9A57107C9A57107 /* SidebarWorkspaceGroupDialogs.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9A57108C9A57108C9A57108 /* SidebarWorkspaceGroupDialogs.swift */; }; + C9A57301C9A57301C9A57301 /* SidebarWorkspaceGroupHeaderMetrics.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9A57302C9A57302C9A57302 /* SidebarWorkspaceGroupHeaderMetrics.swift */; }; + C9A57303C9A57303C9A57303 /* SidebarWorkspaceGroupHeaderMetricsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9A57304C9A57304C9A57304 /* SidebarWorkspaceGroupHeaderMetricsTests.swift */; }; 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 */; }; @@ -1129,6 +1131,8 @@ C135190000000000000000B2 /* SidebarWorkspaceGroupConfigOpenerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarWorkspaceGroupConfigOpenerTests.swift; sourceTree = ""; }; C9A57106C9A57106C9A57106 /* SidebarWorkspaceGroupContextMenuRunner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarWorkspaceGroupContextMenuRunner.swift; sourceTree = ""; }; C9A57108C9A57108C9A57108 /* SidebarWorkspaceGroupDialogs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarWorkspaceGroupDialogs.swift; sourceTree = ""; }; + C9A57302C9A57302C9A57302 /* SidebarWorkspaceGroupHeaderMetrics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarWorkspaceGroupHeaderMetrics.swift; sourceTree = ""; }; + C9A57304C9A57304C9A57304 /* SidebarWorkspaceGroupHeaderMetricsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarWorkspaceGroupHeaderMetricsTests.swift; sourceTree = ""; }; 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 = ""; }; @@ -1519,6 +1523,7 @@ C9A57108C9A57108C9A57108 /* SidebarWorkspaceGroupDialogs.swift */, CA1F0A02CA1F0A02CA1F0A02 /* CmuxModalAlertPresentation.swift */, C9A57102C9A57102C9A57102 /* SidebarWorkspaceGroupHeaderView.swift */, + C9A57302C9A57302C9A57302 /* SidebarWorkspaceGroupHeaderMetrics.swift */, C9A5710AC9A5710AC9A5710A /* SidebarWorkspaceGroupingMetrics.swift */, D5037010000000000000002 /* RenderableSystemSymbol.swift */, C9A57202C9A57202C9A57202 /* SidebarWorkspaceRenderItem.swift */, @@ -1962,6 +1967,7 @@ FEEDC0DEC0DEC0DEC0DE0002 /* FeedCoordinatorTests.swift */, 1D301919B10F22B8708E8883 /* WorkspaceManualUnreadTests.swift */, EE0171AF1F49F7547191CEE5 /* SidebarWidthPolicyTests.swift */, + C9A57304C9A57304C9A57304 /* SidebarWorkspaceGroupHeaderMetricsTests.swift */, 51D800000000000000000002 /* SidebarIdentifierFormattingTests.swift */, C7A507000000000000000001 /* TaskManagerResourcesTests.swift */, C7A507000000000000004530 /* TaskManagerViewSnapshotBoundaryTests.swift */, @@ -2607,6 +2613,7 @@ C9A57103C9A57103C9A57103 /* SidebarWorkspaceGroupConfigOpener.swift in Sources */, C9A57105C9A57105C9A57105 /* SidebarWorkspaceGroupContextMenuRunner.swift in Sources */, C9A57107C9A57107C9A57107 /* SidebarWorkspaceGroupDialogs.swift in Sources */, + C9A57301C9A57301C9A57301 /* SidebarWorkspaceGroupHeaderMetrics.swift in Sources */, C9A57101C9A57101C9A57101 /* SidebarWorkspaceGroupHeaderView.swift in Sources */, C9A57109C9A57109C9A57109 /* SidebarWorkspaceGroupingMetrics.swift in Sources */, C9A57201C9A57201C9A57201 /* SidebarWorkspaceRenderItem.swift in Sources */, @@ -2906,6 +2913,7 @@ CA39C0304FE351A21C372429 /* SidebarWidthPolicyTests.swift in Sources */, D7AB34300000000000000005 /* SidebarWorkspaceDropPlannerTests.swift in Sources */, C135190000000000000000B1 /* SidebarWorkspaceGroupConfigOpenerTests.swift in Sources */, + C9A57303C9A57303C9A57303 /* SidebarWorkspaceGroupHeaderMetricsTests.swift in Sources */, 62270F3DCECB4787D789CCE3 /* SidebarWorkspaceSnapshotRefreshPolicyTests.swift in Sources */, F8000000A1B2C3D4E5F60718 /* SocketControlPasswordStoreTests.swift in Sources */, F6355600A1B2C3D4E5F60718 /* SSHStartupSignalLifecycleTests.swift in Sources */, diff --git a/cmuxTests/SidebarWorkspaceGroupHeaderMetricsTests.swift b/cmuxTests/SidebarWorkspaceGroupHeaderMetricsTests.swift new file mode 100644 index 000000000000..9c5aeec2d2e7 --- /dev/null +++ b/cmuxTests/SidebarWorkspaceGroupHeaderMetricsTests.swift @@ -0,0 +1,53 @@ +import CoreGraphics +import Testing + +#if canImport(cmux_DEV) +@testable import cmux_DEV +#elseif canImport(cmux) +@testable import cmux +#endif + +@Suite struct SidebarWorkspaceGroupHeaderMetricsTests { + @Test func metricsMatchBaseSizesAtDefaultScale() { + let metrics = SidebarWorkspaceGroupHeaderMetrics(fontScale: 1) + + #expect(metrics.chevronFontSize == SidebarWorkspaceGroupHeaderMetrics.baseChevronFontSize) + #expect(metrics.chevronFrame == SidebarWorkspaceGroupHeaderMetrics.baseChevronFrame) + #expect(metrics.iconFontSize == SidebarWorkspaceGroupHeaderMetrics.baseIconFontSize) + #expect(metrics.iconFrame == SidebarWorkspaceGroupHeaderMetrics.baseIconFrame) + #expect(metrics.nameFontSize == SidebarWorkspaceGroupHeaderMetrics.baseNameFontSize) + #expect(metrics.unreadFontSize == SidebarWorkspaceGroupHeaderMetrics.baseUnreadFontSize) + #expect(metrics.unreadHorizontalPadding == SidebarWorkspaceGroupHeaderMetrics.baseUnreadHorizontalPadding) + #expect(metrics.unreadVerticalPadding == SidebarWorkspaceGroupHeaderMetrics.baseUnreadVerticalPadding) + #expect(metrics.plusFontSize == SidebarWorkspaceGroupHeaderMetrics.basePlusFontSize) + #expect(metrics.plusFrame == SidebarWorkspaceGroupHeaderMetrics.basePlusFrame) + } + + @Test func metricsScaleProportionallyWhenSidebarFontEnlarged() { + let scale: CGFloat = 2 + let metrics = SidebarWorkspaceGroupHeaderMetrics(fontScale: scale) + + #expect(metrics.chevronFontSize == SidebarWorkspaceGroupHeaderMetrics.baseChevronFontSize * scale) + #expect(metrics.chevronFrame == SidebarWorkspaceGroupHeaderMetrics.baseChevronFrame * scale) + #expect(metrics.iconFontSize == SidebarWorkspaceGroupHeaderMetrics.baseIconFontSize * scale) + #expect(metrics.iconFrame == SidebarWorkspaceGroupHeaderMetrics.baseIconFrame * scale) + #expect(metrics.nameFontSize == SidebarWorkspaceGroupHeaderMetrics.baseNameFontSize * scale) + #expect(metrics.unreadFontSize == SidebarWorkspaceGroupHeaderMetrics.baseUnreadFontSize * scale) + #expect(metrics.unreadHorizontalPadding == SidebarWorkspaceGroupHeaderMetrics.baseUnreadHorizontalPadding * scale) + #expect(metrics.unreadVerticalPadding == SidebarWorkspaceGroupHeaderMetrics.baseUnreadVerticalPadding * scale) + #expect(metrics.plusFontSize == SidebarWorkspaceGroupHeaderMetrics.basePlusFontSize * scale) + #expect(metrics.plusFrame == SidebarWorkspaceGroupHeaderMetrics.basePlusFrame * scale) + } + + @Test func headerAndRowFontScaleShareOneScalingPath() { + // The header must grow at the same rate as the workspace rows, which + // derive their scale from SidebarTabItemFontScale. Reusing that scale + // keeps the two in lockstep instead of introducing a second path. + let enlargedSize = GhosttyConfig.defaultSidebarFontSize * 1.6 + let rowScale = SidebarTabItemFontScale.scale(for: enlargedSize) + let metrics = SidebarWorkspaceGroupHeaderMetrics(fontScale: rowScale) + + #expect(metrics.nameFontSize == SidebarWorkspaceGroupHeaderMetrics.baseNameFontSize * rowScale) + #expect(rowScale > 1) + } +} From f080b1688bfd91e090a18105198e74440557a74e Mon Sep 17 00:00:00 2001 From: austinpower1258 Date: Thu, 4 Jun 2026 16:41:28 -0700 Subject: [PATCH 2/3] Scale sidebar workspace group header with sidebar font size (#5398) The collapsible workspace group/folder header (chevron, folder icon, group name, unread badge, + button) hardcoded every font size and frame, so it stayed small while the workspace rows grew with the configurable sidebar-font-size. Thread the existing sidebar font scale (settings.sidebarFontScale, the same SidebarTabItemFontScale value that already feeds TabItemView) into SidebarWorkspaceGroupHeaderView and drive every size through SidebarWorkspaceGroupHeaderMetrics, which multiplies each base size by the scale. fontScale joins the Equatable == so a font-size change invalidates the header under the LazyVStack. No second scaling path is introduced; the header and rows now grow at the same rate. Co-Authored-By: Claude Opus 4.8 --- .../SidebarWorkspaceGroupHeaderMetrics.swift | 20 ++++++------- Sources/SidebarWorkspaceGroupHeaderView.swift | 28 +++++++++++-------- .../VerticalTabsSidebar+WorkspaceGroups.swift | 1 + 3 files changed, 28 insertions(+), 21 deletions(-) diff --git a/Sources/SidebarWorkspaceGroupHeaderMetrics.swift b/Sources/SidebarWorkspaceGroupHeaderMetrics.swift index 765d5cc91c73..ca746eb87e87 100644 --- a/Sources/SidebarWorkspaceGroupHeaderMetrics.swift +++ b/Sources/SidebarWorkspaceGroupHeaderMetrics.swift @@ -54,23 +54,23 @@ struct SidebarWorkspaceGroupHeaderMetrics: Equatable { static let basePlusFrame: CGFloat = 18 /// Scaled chevron glyph point size. - var chevronFontSize: CGFloat { Self.baseChevronFontSize } + var chevronFontSize: CGFloat { Self.baseChevronFontSize * fontScale } /// Scaled chevron tap-target frame edge. - var chevronFrame: CGFloat { Self.baseChevronFrame } + var chevronFrame: CGFloat { Self.baseChevronFrame * fontScale } /// Scaled folder/group icon point size. - var iconFontSize: CGFloat { Self.baseIconFontSize } + var iconFontSize: CGFloat { Self.baseIconFontSize * fontScale } /// Scaled folder/group icon frame edge. - var iconFrame: CGFloat { Self.baseIconFrame } + var iconFrame: CGFloat { Self.baseIconFrame * fontScale } /// Scaled group name point size. - var nameFontSize: CGFloat { Self.baseNameFontSize } + var nameFontSize: CGFloat { Self.baseNameFontSize * fontScale } /// Scaled unread badge point size. - var unreadFontSize: CGFloat { Self.baseUnreadFontSize } + var unreadFontSize: CGFloat { Self.baseUnreadFontSize * fontScale } /// Scaled unread badge horizontal padding. - var unreadHorizontalPadding: CGFloat { Self.baseUnreadHorizontalPadding } + var unreadHorizontalPadding: CGFloat { Self.baseUnreadHorizontalPadding * fontScale } /// Scaled unread badge vertical padding. - var unreadVerticalPadding: CGFloat { Self.baseUnreadVerticalPadding } + var unreadVerticalPadding: CGFloat { Self.baseUnreadVerticalPadding * fontScale } /// Scaled plus-button glyph point size. - var plusFontSize: CGFloat { Self.basePlusFontSize } + var plusFontSize: CGFloat { Self.basePlusFontSize * fontScale } /// Scaled plus-button frame edge. - var plusFrame: CGFloat { Self.basePlusFrame } + var plusFrame: CGFloat { Self.basePlusFrame * fontScale } } diff --git a/Sources/SidebarWorkspaceGroupHeaderView.swift b/Sources/SidebarWorkspaceGroupHeaderView.swift index 4d64efc91bca..9c45d697145d 100644 --- a/Sources/SidebarWorkspaceGroupHeaderView.swift +++ b/Sources/SidebarWorkspaceGroupHeaderView.swift @@ -22,6 +22,7 @@ struct SidebarWorkspaceGroupHeaderView: View, Equatable { lhs.showsShortcutHint == rhs.showsShortcutHint && lhs.shortcutHintXOffset == rhs.shortcutHintXOffset && lhs.shortcutHintYOffset == rhs.shortcutHintYOffset && + lhs.fontScale == rhs.fontScale && lhs.cwdContextMenuItems == rhs.cwdContextMenuItems && lhs.newWorkspacePlacement == rhs.newWorkspacePlacement && lhs.rowSpacing == rhs.rowSpacing && @@ -45,6 +46,7 @@ struct SidebarWorkspaceGroupHeaderView: View, Equatable { let showsShortcutHint: Bool let shortcutHintXOffset: Double let shortcutHintYOffset: Double + let fontScale: CGFloat let cwdContextMenuItems: [CmuxResolvedConfigContextMenuItem] let newWorkspacePlacement: WorkspaceGroupNewPlacement? let rowSpacing: CGFloat @@ -67,6 +69,10 @@ struct SidebarWorkspaceGroupHeaderView: View, Equatable { @State private var isHovered = false @State private var rowHeight: CGFloat = 1 + private var metrics: SidebarWorkspaceGroupHeaderMetrics { + SidebarWorkspaceGroupHeaderMetrics(fontScale: fontScale) + } + private var iconColor: Color { if let tintHex, let nsColor = NSColor(hex: tintHex) { return Color(nsColor: nsColor) @@ -100,9 +106,9 @@ struct SidebarWorkspaceGroupHeaderView: View, Equatable { var body: some View { HStack(spacing: 4) { Image(systemName: isCollapsed ? "chevron.right" : "chevron.down") - .font(.system(size: 9, weight: .semibold)) + .font(.system(size: metrics.chevronFontSize, weight: .semibold)) .foregroundStyle(.secondary) - .frame(width: 14, height: 14) + .frame(width: metrics.chevronFrame, height: metrics.chevronFrame) .contentShape(Rectangle()) .onTapGesture { onToggleCollapsed() } .accessibilityAddTraits(.isButton) @@ -116,21 +122,21 @@ struct SidebarWorkspaceGroupHeaderView: View, Equatable { HStack(spacing: 6) { Image(systemName: displayedIconSymbol) - .font(.system(size: 11, weight: .semibold)) + .font(.system(size: metrics.iconFontSize, weight: .semibold)) .foregroundStyle(iconColor) - .frame(width: 14, height: 14) + .frame(width: metrics.iconFrame, height: metrics.iconFrame) .accessibilityHidden(true) Text(name) - .font(.system(size: 11, weight: .semibold)) + .font(.system(size: metrics.nameFontSize, weight: .semibold)) .foregroundStyle(isAnchorActive ? Color.primary : Color.primary.opacity(0.9)) .lineLimit(1) .truncationMode(.tail) if anchorUnreadCount > 0 { Text("\(anchorUnreadCount)") - .font(.system(size: 10, weight: .semibold)) + .font(.system(size: metrics.unreadFontSize, weight: .semibold)) .foregroundStyle(.white) - .padding(.horizontal, 5) - .padding(.vertical, 1) + .padding(.horizontal, metrics.unreadHorizontalPadding) + .padding(.vertical, metrics.unreadVerticalPadding) .background(Capsule().fill(Color.accentColor)) .accessibilityLabel(Text(String.localizedStringWithFormat( String(localized: "workspaceGroup.unread.a11y", defaultValue: "%lld unread"), @@ -151,14 +157,14 @@ struct SidebarWorkspaceGroupHeaderView: View, Equatable { let plusVisible = isHovered && !showsShortcutHint Button(action: onTapPlus) { Image(systemName: "plus") - .font(.system(size: 11, weight: .medium)) + .font(.system(size: metrics.plusFontSize, weight: .medium)) .foregroundStyle(.secondary) - .frame(width: 18, height: 18) + .frame(width: metrics.plusFrame, height: metrics.plusFrame) .contentShape(Rectangle()) .opacity(plusVisible ? 1 : 0) } .buttonStyle(.plain) - .frame(width: 18, height: 18) + .frame(width: metrics.plusFrame, height: metrics.plusFrame) .allowsHitTesting(plusVisible) .accessibilityHidden(!plusVisible) .accessibilityLabel(Text(String( diff --git a/Sources/VerticalTabsSidebar+WorkspaceGroups.swift b/Sources/VerticalTabsSidebar+WorkspaceGroups.swift index 60e3c351dac6..e11fb6b8f6b4 100644 --- a/Sources/VerticalTabsSidebar+WorkspaceGroups.swift +++ b/Sources/VerticalTabsSidebar+WorkspaceGroups.swift @@ -90,6 +90,7 @@ extension VerticalTabsSidebar { showsShortcutHint: showsHintForAnchor, shortcutHintXOffset: settings.sidebarShortcutHintXOffset, shortcutHintYOffset: settings.sidebarShortcutHintYOffset, + fontScale: settings.sidebarFontScale, cwdContextMenuItems: cwdContextMenuItems, newWorkspacePlacement: newWorkspacePlacement, rowSpacing: tabRowSpacing, From 933425b23448345e45ff6de631c9ac2ec2bef7a4 Mon Sep 17 00:00:00 2001 From: austinpower1258 Date: Thu, 4 Jun 2026 17:03:43 -0700 Subject: [PATCH 3/3] Add scale-down coverage for sidebar group header metrics (#5398) Address CodeRabbit review: assert the metrics shrink proportionally at fontScale < 1 (0.5) in addition to the enlarged (2.0) and default (1.0) cases, covering the full proportional-scaling contract. Co-Authored-By: Claude Opus 4.8 --- .claude/scheduled_tasks.lock | 2 +- ...SidebarWorkspaceGroupHeaderMetricsTests.swift | 16 ++++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/.claude/scheduled_tasks.lock b/.claude/scheduled_tasks.lock index cc524f1be7c9..20959ecc454a 100644 --- a/.claude/scheduled_tasks.lock +++ b/.claude/scheduled_tasks.lock @@ -1 +1 @@ -{"sessionId":"9c9943de-bc79-4f2c-9b82-cbd58c3a1d51","pid":32319,"procStart":"Sun May 31 00:20:09 2026","acquiredAt":1780187614705} \ No newline at end of file +{"sessionId":"5c2a62d0-99c2-42c0-9452-f67b6fbaacda","pid":75304,"procStart":"Thu Jun 4 23:34:54 2026","acquiredAt":1780616564932} \ No newline at end of file diff --git a/cmuxTests/SidebarWorkspaceGroupHeaderMetricsTests.swift b/cmuxTests/SidebarWorkspaceGroupHeaderMetricsTests.swift index 9c5aeec2d2e7..dc5c1031f9f3 100644 --- a/cmuxTests/SidebarWorkspaceGroupHeaderMetricsTests.swift +++ b/cmuxTests/SidebarWorkspaceGroupHeaderMetricsTests.swift @@ -39,6 +39,22 @@ import Testing #expect(metrics.plusFrame == SidebarWorkspaceGroupHeaderMetrics.basePlusFrame * scale) } + @Test func metricsScaleProportionallyWhenSidebarFontShrunk() { + let scale: CGFloat = 0.5 + let metrics = SidebarWorkspaceGroupHeaderMetrics(fontScale: scale) + + #expect(metrics.chevronFontSize == SidebarWorkspaceGroupHeaderMetrics.baseChevronFontSize * scale) + #expect(metrics.chevronFrame == SidebarWorkspaceGroupHeaderMetrics.baseChevronFrame * scale) + #expect(metrics.iconFontSize == SidebarWorkspaceGroupHeaderMetrics.baseIconFontSize * scale) + #expect(metrics.iconFrame == SidebarWorkspaceGroupHeaderMetrics.baseIconFrame * scale) + #expect(metrics.nameFontSize == SidebarWorkspaceGroupHeaderMetrics.baseNameFontSize * scale) + #expect(metrics.unreadFontSize == SidebarWorkspaceGroupHeaderMetrics.baseUnreadFontSize * scale) + #expect(metrics.unreadHorizontalPadding == SidebarWorkspaceGroupHeaderMetrics.baseUnreadHorizontalPadding * scale) + #expect(metrics.unreadVerticalPadding == SidebarWorkspaceGroupHeaderMetrics.baseUnreadVerticalPadding * scale) + #expect(metrics.plusFontSize == SidebarWorkspaceGroupHeaderMetrics.basePlusFontSize * scale) + #expect(metrics.plusFrame == SidebarWorkspaceGroupHeaderMetrics.basePlusFrame * scale) + } + @Test func headerAndRowFontScaleShareOneScalingPath() { // The header must grow at the same rate as the workspace rows, which // derive their scale from SidebarTabItemFontScale. Reusing that scale