Skip to content
Open
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
33c15bc
fix: restore sidebar workspace icon size
austinywang May 29, 2026
d38650b
fix: guard sidebar icon row metrics
austinywang May 29, 2026
2f91595
fix: clarify sidebar icon metric invariant
austinywang May 29, 2026
abdd78a
test: expect shared sidebar chrome icon metrics
austinywang May 29, 2026
8865058
fix: restore sidebar chrome icon size
austinywang May 29, 2026
0f6cd6b
test: restore release titlebar icon metrics
austinywang May 29, 2026
aaddc23
fix: restore release titlebar icon scale
austinywang May 29, 2026
99dab20
fix: restore release sidebar toggle symbol
austinywang May 29, 2026
7dc55c5
test: require titlebar controls to fit sidebar
austinywang May 29, 2026
984a12f
fix: fit release-size titlebar controls in sidebar
austinywang May 29, 2026
a51ce49
test: require sidebar-width titlebar spacing
austinywang May 29, 2026
73a618c
fix: fill sidebar titlebar lane with release spacing
austinywang May 29, 2026
c8afb1c
fix: return computed titlebar control size
austinywang May 29, 2026
fb661ee
test: keep sidebar titlebar chrome to release slots
austinywang May 29, 2026
80d3e91
fix: keep focus history out of sidebar chrome
austinywang May 29, 2026
5e1a5ed
fix: use release slots in titlebar accessory
austinywang May 29, 2026
696c750
fix: derive sidebar titlebar slot count
austinywang May 30, 2026
7d6a391
fix: reserve sidebar titlebar hint space
austinywang May 30, 2026
8dc7bbd
fix: constrain sidebar titlebar chrome styles
austinywang May 30, 2026
b7ceab1
fix: avoid crashing on sidebar icon padding mismatch
austinywang May 30, 2026
13ee964
merge: resolve conflicts with main
austinywang Jun 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 38 additions & 9 deletions Sources/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10884,14 +10884,14 @@ struct VerticalTabsSidebar: View {
isSelected: Bool,
dropRows: [ExtensionSidebarBrowserStackDropRow]
) -> some View {
let targetRowHeight: CGFloat = 54
let targetRowHeight = ExtensionBrowserStackSidebarMetrics.tileHeight

return Button {
selectExtensionSidebarWorkspace(row.workspaceId)
} label: {
extensionBrowserStackIcon(row.leadingIcon, size: 28)
extensionBrowserStackIcon(row.leadingIcon, size: ExtensionBrowserStackSidebarMetrics.iconSize)
.frame(maxWidth: .infinity)
.frame(height: 54)
.frame(height: targetRowHeight)
.background(
RoundedRectangle(cornerRadius: 13, style: .continuous)
.fill(
Expand Down Expand Up @@ -10957,13 +10957,16 @@ struct VerticalTabsSidebar: View {
isSelected: Bool,
dropRows: [ExtensionSidebarBrowserStackDropRow]
) -> some View {
let targetRowHeight: CGFloat = compact ? 34 : 38
let targetRowHeight = ExtensionBrowserStackSidebarMetrics.rowHeight(compact: compact)
let rowHorizontalPadding = ExtensionBrowserStackSidebarMetrics.rowHorizontalPadding(compact: compact)
let rowVerticalPadding = ExtensionBrowserStackSidebarMetrics.rowVerticalPadding(compact: compact)
let rowCornerRadius = ExtensionBrowserStackSidebarMetrics.rowCornerRadius(compact: compact)

return Button {
selectExtensionSidebarWorkspace(row.workspaceId)
} label: {
HStack(spacing: 9) {
extensionBrowserStackIcon(row.leadingIcon, size: compact ? 22 : 24)
extensionBrowserStackIcon(row.leadingIcon, size: ExtensionBrowserStackSidebarMetrics.iconSize)
Text(row.title)
.font(.system(size: compact ? 12.5 : 13, weight: .medium))
.foregroundColor(isSelected ? .primary : .primary.opacity(0.82))
Expand All @@ -10977,14 +10980,14 @@ struct VerticalTabsSidebar: View {
.lineLimit(1)
}
}
.padding(.horizontal, compact ? 7 : 10)
.padding(.vertical, compact ? 6 : 7)
.padding(.horizontal, rowHorizontalPadding)
.padding(.vertical, rowVerticalPadding)
.background(
RoundedRectangle(cornerRadius: compact ? 8 : 10, style: .continuous)
RoundedRectangle(cornerRadius: rowCornerRadius, style: .continuous)
.fill(isSelected ? Color.primary.opacity(0.12) : Color.clear)
)
.overlay(
RoundedRectangle(cornerRadius: compact ? 8 : 10, style: .continuous)
RoundedRectangle(cornerRadius: rowCornerRadius, style: .continuous)
.stroke(isSelected ? cmuxAccentColor().opacity(0.55) : Color.clear, lineWidth: 1)
)
.contentShape(Rectangle())
Expand Down Expand Up @@ -11128,6 +11131,32 @@ struct VerticalTabsSidebar: View {
return snapshotsById
}

private enum ExtensionBrowserStackSidebarMetrics {
static let iconSize: CGFloat = 28
static let tileHeight: CGFloat = 54

private static let regularRowHeight: CGFloat = 38
private static let compactRowHeight: CGFloat = 34

static func rowHeight(compact: Bool) -> CGFloat {
compact ? compactRowHeight : regularRowHeight
}

static func rowHorizontalPadding(compact: Bool) -> CGFloat {
compact ? 7 : 10
}

static func rowVerticalPadding(compact: Bool) -> CGFloat {
let height = rowHeight(compact: compact)
precondition(iconSize <= height, "iconSize (\(iconSize)) must not exceed rowHeight (\(height))")
return (height - iconSize) / 2
}
Comment thread
austinywang marked this conversation as resolved.
Comment thread
austinywang marked this conversation as resolved.

static func rowCornerRadius(compact: Bool) -> CGFloat {
compact ? 8 : 10
}
}

private func extensionBrowserStackIcon(
_ icon: CmuxExtensionSidebarRenderIcon?,
size: CGFloat
Expand Down
3 changes: 1 addition & 2 deletions Sources/RightSidebarChromeStyle.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,8 @@ enum HeaderChromeIconStyle {
static let hoveredOpacity = 0.96
static let pressedOpacity = 1.0
static let disabledOpacity = 0.34
static let weight: Font.Weight = .regular
static let weight: Font.Weight = .semibold
static let foregroundColor = Color(nsColor: .secondaryLabelColor)
static let sidebarGlyphStrokeWidth: CGFloat = 1

static func iconFrameSize(forIconSize iconSize: CGFloat) -> CGFloat {
HeaderChromeControlMetrics.iconFrameSize(forIconSize: iconSize)
Expand Down
64 changes: 51 additions & 13 deletions Sources/Update/MinimalModeSidebarControls.swift
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import SwiftUI

struct MinimalModeSidebarControlActionProxyView: NSViewRepresentable {
let config: TitlebarControlsStyleConfig
var buttonCount = TitlebarControlsHitRegions.sidebarChromeButtonCount
var isEnabled = true
var requiresRevealedState = false
let onAction: (MinimalModeSidebarControlActionSlot, NSView, NSPoint) -> Void
Expand All @@ -20,6 +21,7 @@ struct MinimalModeSidebarControlActionProxyView: NSViewRepresentable {

private func configure(_ view: MinimalModeSidebarControlActionView) {
view.config = config
view.buttonCount = buttonCount
view.isEnabled = isEnabled
view.requiresRevealedState = requiresRevealedState
view.onAction = onAction
Expand All @@ -28,14 +30,19 @@ struct MinimalModeSidebarControlActionProxyView: NSViewRepresentable {

enum TitlebarControlsHitRegions {
static let outerLeadingPadding: CGFloat = 4
static let buttonCount = MinimalModeSidebarControlActionSlot.allCases.count
static let sidebarChromeButtonCount = TitlebarShortcutHintActionSlot.sidebarChromeSlots.count
static let allTitlebarButtonCount = MinimalModeSidebarControlActionSlot.allCases.count

static func buttonXRanges(config: TitlebarControlsStyleConfig) -> [ClosedRange<CGFloat>] {
static func buttonXRanges(
config: TitlebarControlsStyleConfig,
buttonCount: Int = sidebarChromeButtonCount
) -> [ClosedRange<CGFloat>] {
var ranges: [ClosedRange<CGFloat>] = []
ranges.reserveCapacity(buttonCount)
let clampedButtonCount = max(0, min(buttonCount, allTitlebarButtonCount))
ranges.reserveCapacity(clampedButtonCount)

var minX = outerLeadingPadding + config.groupPadding.leading
for _ in 0..<buttonCount {
for _ in 0..<clampedButtonCount {
let maxX = minX + config.buttonSize
ranges.append(minX...maxX)
minX = maxX + config.spacing
Expand All @@ -46,16 +53,22 @@ enum TitlebarControlsHitRegions {

static func sidebarActionSlot(
at point: NSPoint,
config: TitlebarControlsStyleConfig
config: TitlebarControlsStyleConfig,
buttonCount: Int = sidebarChromeButtonCount
) -> MinimalModeSidebarControlActionSlot? {
for (index, range) in buttonXRanges(config: config).enumerated() where range.contains(point.x) {
for (index, range) in buttonXRanges(config: config, buttonCount: buttonCount).enumerated()
where range.contains(point.x) {
return MinimalModeSidebarControlActionSlot(rawValue: index)
}
return nil
}

static func pointFallsInButtonColumn(_ point: NSPoint, config: TitlebarControlsStyleConfig) -> Bool {
sidebarActionSlot(at: point, config: config) != nil
static func pointFallsInButtonColumn(
_ point: NSPoint,
config: TitlebarControlsStyleConfig,
buttonCount: Int = sidebarChromeButtonCount
) -> Bool {
sidebarActionSlot(at: point, config: config, buttonCount: buttonCount) != nil
}
}

Expand All @@ -64,6 +77,10 @@ final class MinimalModeSidebarControlActionView: NSView {
{
didSet { needsLayout = true }
}
var buttonCount = TitlebarControlsHitRegions.sidebarChromeButtonCount
{
didSet { needsLayout = true }
}
var isEnabled = true
{
didSet { syncButtons() }
Expand Down Expand Up @@ -139,7 +156,7 @@ final class MinimalModeSidebarControlActionView: NSView {

override func accessibilityChildren() -> [Any]? {
guard isRevealed || !requiresRevealedState else { return [] }
return MinimalModeSidebarControlActionSlot.allCases.compactMap { buttons[$0] }
return visibleSlots.compactMap { buttons[$0] }
}

override func acceptsFirstMouse(for event: NSEvent?) -> Bool {
Expand All @@ -153,7 +170,11 @@ final class MinimalModeSidebarControlActionView: NSView {
return nil
}
guard bounds.contains(point) else { return nil }
guard let slot = TitlebarControlsHitRegions.sidebarActionSlot(at: point, config: config) else {
guard let slot = TitlebarControlsHitRegions.sidebarActionSlot(
at: point,
config: config,
buttonCount: buttonCount
) else {
return nil
}
if NSApp.currentEvent?.type == .rightMouseDown, !slot.acceptsContextMenu {
Expand All @@ -175,7 +196,11 @@ final class MinimalModeSidebarControlActionView: NSView {

override func mouseDown(with event: NSEvent) {
let localPoint = convert(event.locationInWindow, from: nil)
guard let slot = TitlebarControlsHitRegions.sidebarActionSlot(at: localPoint, config: config) else {
guard let slot = TitlebarControlsHitRegions.sidebarActionSlot(
at: localPoint,
config: config,
buttonCount: buttonCount
) else {
super.mouseDown(with: event)
return
}
Expand All @@ -188,7 +213,11 @@ final class MinimalModeSidebarControlActionView: NSView {

override func rightMouseDown(with event: NSEvent) {
let localPoint = convert(event.locationInWindow, from: nil)
guard let slot = TitlebarControlsHitRegions.sidebarActionSlot(at: localPoint, config: config),
guard let slot = TitlebarControlsHitRegions.sidebarActionSlot(
at: localPoint,
config: config,
buttonCount: buttonCount
),
shouldAcceptAction(at: localPoint) else {
super.rightMouseDown(with: event)
return
Expand All @@ -207,7 +236,10 @@ final class MinimalModeSidebarControlActionView: NSView {

override func layout() {
super.layout()
let ranges = TitlebarControlsHitRegions.buttonXRanges(config: config)
for button in buttons.values {
button.frame = .zero
}
let ranges = TitlebarControlsHitRegions.buttonXRanges(config: config, buttonCount: buttonCount)
for (index, range) in ranges.enumerated() {
guard let slot = MinimalModeSidebarControlActionSlot(rawValue: index),
let button = buttons[slot] else { continue }
Expand All @@ -221,6 +253,12 @@ final class MinimalModeSidebarControlActionView: NSView {
syncButtons()
}

private var visibleSlots: ArraySlice<MinimalModeSidebarControlActionSlot> {
MinimalModeSidebarControlActionSlot.allCases.prefix(
max(0, min(buttonCount, TitlebarControlsHitRegions.allTitlebarButtonCount))
)
}

@objc private func buttonPressed(_ sender: NSButton) {
guard let sender = sender as? MinimalModeSidebarControlButton else { return }
performButtonAction(sender)
Expand Down
Loading
Loading