From 6c644d930de89671c3bc20283f29b6ba178dc168 Mon Sep 17 00:00:00 2001 From: Lawrence Chen <54008264+lawrencecchen@users.noreply.github.com> Date: Fri, 13 Mar 2026 21:40:25 -0700 Subject: [PATCH] Allow smaller sidebar widths (#1420) * Add sidebar minimum width UI regression test * Allow narrower sidebar resizing * Set smaller sidebar minimum to 180 --- Sources/ContentView.swift | 30 ++++++++++++++----------- Sources/SessionPersistence.swift | 2 +- cmuxTests/SidebarWidthPolicyTests.swift | 17 ++++++++++++++ cmuxUITests/SidebarResizeUITests.swift | 24 ++++++++++++++++++++ 4 files changed, 59 insertions(+), 14 deletions(-) create mode 100644 cmuxTests/SidebarWidthPolicyTests.swift diff --git a/Sources/ContentView.swift b/Sources/ContentView.swift index 2a6fdb3c..5f25af79 100644 --- a/Sources/ContentView.swift +++ b/Sources/ContentView.swift @@ -1646,7 +1646,7 @@ struct ContentView: View { nonisolated private static let commandPaletteCommandsPrefix = ">" private static let commandPaletteVisiblePreviewResultLimit = 48 private static let commandPaletteVisiblePreviewCandidateLimit = 192 - private static let minimumSidebarWidth: CGFloat = 186 + private static let minimumSidebarWidth: CGFloat = CGFloat(SessionPersistencePolicy.minimumSidebarWidth) private static let maximumSidebarWidthRatio: CGFloat = 1.0 / 3.0 private enum SidebarResizerHandle: Hashable { @@ -1673,10 +1673,19 @@ struct ContentView: View { return max(Self.minimumSidebarWidth, fallbackScreenWidth * Self.maximumSidebarWidthRatio) } + static func clampedSidebarWidth(_ candidate: CGFloat, maximumWidth: CGFloat) -> CGFloat { + let minimumWidth = Self.minimumSidebarWidth + let sanitizedMaximumWidth = max(minimumWidth, maximumWidth.isFinite ? maximumWidth : minimumWidth) + guard candidate.isFinite else { + return CGFloat(SessionPersistencePolicy.defaultSidebarWidth) + } + return max(minimumWidth, min(sanitizedMaximumWidth, candidate)) + } + private func clampSidebarWidthIfNeeded(availableWidth: CGFloat? = nil) { - let nextWidth = max( - Self.minimumSidebarWidth, - min(maxSidebarWidth(availableWidth: availableWidth), sidebarWidth) + let nextWidth = Self.clampedSidebarWidth( + sidebarWidth, + maximumWidth: maxSidebarWidth(availableWidth: availableWidth) ) guard abs(nextWidth - sidebarWidth) > 0.5 else { return } withTransaction(Transaction(animation: nil)) { @@ -1685,12 +1694,7 @@ struct ContentView: View { } private func normalizedSidebarWidth(_ candidate: CGFloat) -> CGFloat { - let minWidth = CGFloat(SessionPersistencePolicy.minimumSidebarWidth) - let maxWidth = max(minWidth, maxSidebarWidth()) - if !candidate.isFinite { - return CGFloat(SessionPersistencePolicy.defaultSidebarWidth) - } - return max(minWidth, min(maxWidth, candidate)) + Self.clampedSidebarWidth(candidate, maximumWidth: maxSidebarWidth()) } private func activateSidebarResizerCursor() { @@ -1881,9 +1885,9 @@ struct ContentView: View { activateSidebarResizerCursor() let startWidth = sidebarDragStartWidth ?? sidebarWidth - let nextWidth = max( - Self.minimumSidebarWidth, - min(maxSidebarWidth(availableWidth: availableWidth), startWidth + value.translation.width) + let nextWidth = Self.clampedSidebarWidth( + startWidth + value.translation.width, + maximumWidth: maxSidebarWidth(availableWidth: availableWidth) ) withTransaction(Transaction(animation: nil)) { sidebarWidth = nextWidth diff --git a/Sources/SessionPersistence.swift b/Sources/SessionPersistence.swift index 53eb995e..5419cd92 100644 --- a/Sources/SessionPersistence.swift +++ b/Sources/SessionPersistence.swift @@ -8,7 +8,7 @@ enum SessionSnapshotSchema { enum SessionPersistencePolicy { static let defaultSidebarWidth: Double = 200 - static let minimumSidebarWidth: Double = 186 + static let minimumSidebarWidth: Double = 180 static let maximumSidebarWidth: Double = 600 static let minimumWindowWidth: Double = 300 static let minimumWindowHeight: Double = 200 diff --git a/cmuxTests/SidebarWidthPolicyTests.swift b/cmuxTests/SidebarWidthPolicyTests.swift new file mode 100644 index 00000000..78a5fbb1 --- /dev/null +++ b/cmuxTests/SidebarWidthPolicyTests.swift @@ -0,0 +1,17 @@ +import XCTest + +#if canImport(cmux_DEV) +@testable import cmux_DEV +#elseif canImport(cmux) +@testable import cmux +#endif + +final class SidebarWidthPolicyTests: XCTestCase { + func testContentViewClampAllowsNarrowSidebarBelowLegacyMinimum() { + XCTAssertEqual( + ContentView.clampedSidebarWidth(184, maximumWidth: 600), + 184, + accuracy: 0.001 + ) + } +} diff --git a/cmuxUITests/SidebarResizeUITests.swift b/cmuxUITests/SidebarResizeUITests.swift index cdca1146..a29d0c96 100644 --- a/cmuxUITests/SidebarResizeUITests.swift +++ b/cmuxUITests/SidebarResizeUITests.swift @@ -37,6 +37,30 @@ final class SidebarResizeUITests: XCTestCase { XCTAssertGreaterThanOrEqual(leftDelta, -122, "Resizer moved farther than requested drag-left offset") } + func testSidebarResizerAllowsSmallerMinimumWidth() { + let app = XCUIApplication() + app.launch() + + let window = app.windows.firstMatch + XCTAssertTrue(window.waitForExistence(timeout: 5.0)) + + let elements = app.descendants(matching: .any) + let resizer = elements["SidebarResizer"] + XCTAssertTrue(resizer.waitForExistence(timeout: 5.0)) + XCTAssertTrue(waitForElementHittable(resizer, timeout: 5.0), "Expected sidebar resizer to become hittable") + + let start = resizer.coordinate(withNormalizedOffset: CGVector(dx: 0.5, dy: 0.5)) + let farLeft = start.withOffset(CGVector(dx: -max(200, window.frame.width), dy: 0)) + start.press(forDuration: 0.1, thenDragTo: farLeft) + + let sidebarWidth = max(0, resizer.frame.midX - window.frame.minX) + XCTAssertLessThanOrEqual( + sidebarWidth, + 185, + "Expected sidebar minimum width to allow a narrower sidebar than the previous 186 px floor. width=\(sidebarWidth)" + ) + } + func testSidebarResizerHasMaximumWidthCap() { let app = XCUIApplication() app.launch()