From 2fd82eedbb98d675894a130aaf1235e7a603283d Mon Sep 17 00:00:00 2001 From: Abdulaziz Albahar <67667005+azooz2003-bit@users.noreply.github.com> Date: Mon, 21 Sep 2026 21:07:11 -0700 Subject: [PATCH 1/2] Add edge fade to Files filter chips --- .../HorizontalEdgeFadePillBar.swift | 49 ++++++++++++++++++ ...zontalEdgeFadePillBarViewController.swift} | 32 ++++++------ ...ift => HorizontalEdgeFadeScrollView.swift} | 4 +- .../TaskComposer/TaskComposerLayout.swift | 2 +- .../TaskComposer/TaskComposerPillBar.swift | 40 --------------- .../TerminalArtifactFilesSheet+Content.swift | 50 +++++++++++-------- .../HorizontalEdgeFadeTests.swift | 24 +++++++++ .../TaskComposerEdgeFadeTests.swift | 24 --------- 8 files changed, 122 insertions(+), 103 deletions(-) create mode 100644 Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/HorizontalEdgeFadePillBar.swift rename Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/{TaskComposer/TaskComposerPillBarViewController.swift => HorizontalEdgeFadePillBarViewController.swift} (85%) rename Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/{TaskComposer/TaskComposerEdgeFadeScrollView.swift => HorizontalEdgeFadeScrollView.swift} (94%) delete mode 100644 Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerPillBar.swift create mode 100644 Packages/iOS/CmuxMobileShellUI/Tests/CmuxMobileShellUITests/HorizontalEdgeFadeTests.swift delete mode 100644 Packages/iOS/CmuxMobileShellUI/Tests/CmuxMobileShellUITests/TaskComposerEdgeFadeTests.swift diff --git a/Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/HorizontalEdgeFadePillBar.swift b/Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/HorizontalEdgeFadePillBar.swift new file mode 100644 index 000000000000..3fe2c73bf4b9 --- /dev/null +++ b/Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/HorizontalEdgeFadePillBar.swift @@ -0,0 +1,49 @@ +#if os(iOS) +import SwiftUI +import UIKit + +/// Hosts horizontally scrolling pills in a real UIKit scroll view while keeping +/// its fixed controls above the scrolling content at either edge. +struct HorizontalEdgeFadePillBar: UIViewControllerRepresentable { + let contentInsets: UIEdgeInsets + let accessibilityIdentifier: String + let leading: Leading + let pills: Pills + let trailing: Trailing + + init( + contentInsets: UIEdgeInsets = UIEdgeInsets(top: 0, left: 8, bottom: 0, right: 8), + accessibilityIdentifier: String, + @ViewBuilder leading: () -> Leading, + @ViewBuilder pills: () -> Pills, + @ViewBuilder trailing: () -> Trailing + ) { + self.contentInsets = contentInsets + self.accessibilityIdentifier = accessibilityIdentifier + self.leading = leading() + self.pills = pills() + self.trailing = trailing() + } + + func makeUIViewController(context: Context) -> HorizontalEdgeFadePillBarViewController { + HorizontalEdgeFadePillBarViewController( + contentInsets: contentInsets, + accessibilityIdentifier: accessibilityIdentifier, + leading: leading, + pills: pills, + trailing: trailing + ) + } + + func updateUIViewController( + _ viewController: HorizontalEdgeFadePillBarViewController, + context: Context + ) { + viewController.update( + leading: leading, + pills: pills, + trailing: trailing + ) + } +} +#endif diff --git a/Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerPillBarViewController.swift b/Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/HorizontalEdgeFadePillBarViewController.swift similarity index 85% rename from Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerPillBarViewController.swift rename to Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/HorizontalEdgeFadePillBarViewController.swift index 352f94abfb5f..9f110cfef61e 100644 --- a/Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerPillBarViewController.swift +++ b/Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/HorizontalEdgeFadePillBarViewController.swift @@ -2,12 +2,12 @@ import SwiftUI import UIKit -/// Owns the task composer's scroll geometry and the fixed controls that flank +/// Owns horizontal pill scroll geometry and the fixed controls that flank /// its horizontally scrolling pills, following the terminal accessory bar's /// bounded viewport layout. @MainActor -final class TaskComposerPillBarViewController: UIViewController { - private let scrollView = TaskComposerEdgeFadeScrollView() +final class HorizontalEdgeFadePillBarViewController: UIViewController { + private let scrollView = HorizontalEdgeFadeScrollView() private let leadingHost: UIHostingController private let pillsHost: UIHostingController private let trailingHost: UIHostingController @@ -15,9 +15,18 @@ final class TaskComposerPillBarViewController: UIViewControllerRepresentable { - let leading: Leading - let pills: Pills - let trailing: Trailing - - init( - @ViewBuilder leading: () -> Leading, - @ViewBuilder pills: () -> Pills, - @ViewBuilder trailing: () -> Trailing - ) { - self.leading = leading() - self.pills = pills() - self.trailing = trailing() - } - - func makeUIViewController(context: Context) -> TaskComposerPillBarViewController { - TaskComposerPillBarViewController( - leading: leading, - pills: pills, - trailing: trailing - ) - } - - func updateUIViewController( - _ viewController: TaskComposerPillBarViewController, - context: Context - ) { - viewController.update( - leading: leading, - pills: pills, - trailing: trailing - ) - } -} -#endif diff --git a/Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TerminalArtifactFilesSheet+Content.swift b/Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TerminalArtifactFilesSheet+Content.swift index d8b62b7c6383..4c3e081bf4bd 100644 --- a/Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TerminalArtifactFilesSheet+Content.swift +++ b/Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TerminalArtifactFilesSheet+Content.swift @@ -2,6 +2,7 @@ import CmuxAgentChat import CmuxAgentChatUI import SwiftUI +import UIKit extension TerminalArtifactFilesSheet { var scopePicker: some View { @@ -571,31 +572,35 @@ extension TerminalArtifactFilesSheet { private static let sessionTopTolerance: CGFloat = 1 private var galleryControls: some View { - HStack(spacing: 12) { - ScrollView(.horizontal, showsIndicators: false) { - HStack(spacing: 8) { - ForEach(ChatArtifactGalleryFilter.allCases, id: \.self) { filter in - Button { - galleryFilter = filter - } label: { - Text(filterTitle(filter)) - .font(.subheadline.weight(.medium)) - .foregroundStyle(galleryFilter == filter ? Color.white : Color.primary) - .padding(.horizontal, 12) - .padding(.vertical, 7) - .background( - galleryFilter == filter - ? Color.accentColor - : Color(uiColor: .secondarySystemBackground), - in: Capsule() - ) - } - .buttonStyle(.plain) - .accessibilityAddTraits(galleryFilter == filter ? .isSelected : []) + HorizontalEdgeFadePillBar( + contentInsets: UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 12), + accessibilityIdentifier: "TerminalArtifactGalleryFilterScroller" + ) { + EmptyView() + } pills: { + HStack(spacing: 8) { + ForEach(ChatArtifactGalleryFilter.allCases, id: \.self) { filter in + Button { + galleryFilter = filter + } label: { + Text(filterTitle(filter)) + .font(.subheadline.weight(.medium)) + .foregroundStyle(galleryFilter == filter ? Color.white : Color.primary) + .padding(.horizontal, 12) + .padding(.vertical, 7) + .background( + galleryFilter == filter + ? Color.accentColor + : Color(uiColor: .secondarySystemBackground), + in: Capsule() + ) } + .buttonStyle(.plain) + .accessibilityAddTraits(galleryFilter == filter ? .isSelected : []) } } - + .fixedSize() + } trailing: { TerminalArtifactGallerySortMenu( value: TerminalArtifactGallerySortMenuValue(sort: gallerySort), actions: TerminalArtifactGallerySortMenuActions( @@ -604,6 +609,7 @@ extension TerminalArtifactFilesSheet { ) .equatable() } + .frame(height: 44) .padding(.horizontal, 16) .padding(.vertical, 10) } diff --git a/Packages/iOS/CmuxMobileShellUI/Tests/CmuxMobileShellUITests/HorizontalEdgeFadeTests.swift b/Packages/iOS/CmuxMobileShellUI/Tests/CmuxMobileShellUITests/HorizontalEdgeFadeTests.swift new file mode 100644 index 000000000000..a1af84b65b58 --- /dev/null +++ b/Packages/iOS/CmuxMobileShellUI/Tests/CmuxMobileShellUITests/HorizontalEdgeFadeTests.swift @@ -0,0 +1,24 @@ +import Testing + +@testable import CmuxMobileShellUI + +@Suite("Horizontal edge fade") +struct HorizontalEdgeFadeTests { + @Test("edge stays opaque at rest") + func opaqueAtRest() { + #expect(HorizontalEdgeFadeScrollView.edgeAlpha(distance: 0) == 1) + #expect(HorizontalEdgeFadeScrollView.edgeAlpha(distance: -4) == 1) + } + + @Test("fade ramps linearly as content approaches a control edge") + func incrementalRamp() { + #expect(abs(HorizontalEdgeFadeScrollView.edgeAlpha(distance: 6) - 0.75) < 0.0001) + #expect(abs(HorizontalEdgeFadeScrollView.edgeAlpha(distance: 12) - 0.5) < 0.0001) + } + + @Test("fade saturates after one band") + func saturates() { + #expect(HorizontalEdgeFadeScrollView.edgeAlpha(distance: 24) == 0) + #expect(HorizontalEdgeFadeScrollView.edgeAlpha(distance: 500) == 0) + } +} diff --git a/Packages/iOS/CmuxMobileShellUI/Tests/CmuxMobileShellUITests/TaskComposerEdgeFadeTests.swift b/Packages/iOS/CmuxMobileShellUI/Tests/CmuxMobileShellUITests/TaskComposerEdgeFadeTests.swift deleted file mode 100644 index 58934862e41e..000000000000 --- a/Packages/iOS/CmuxMobileShellUI/Tests/CmuxMobileShellUITests/TaskComposerEdgeFadeTests.swift +++ /dev/null @@ -1,24 +0,0 @@ -import Testing - -@testable import CmuxMobileShellUI - -@Suite("Task composer horizontal edge fade") -struct TaskComposerEdgeFadeTests { - @Test("edge stays opaque at rest") - func opaqueAtRest() { - #expect(TaskComposerEdgeFadeScrollView.edgeAlpha(distance: 0) == 1) - #expect(TaskComposerEdgeFadeScrollView.edgeAlpha(distance: -4) == 1) - } - - @Test("fade ramps linearly as content approaches a control edge") - func incrementalRamp() { - #expect(abs(TaskComposerEdgeFadeScrollView.edgeAlpha(distance: 6) - 0.75) < 0.0001) - #expect(abs(TaskComposerEdgeFadeScrollView.edgeAlpha(distance: 12) - 0.5) < 0.0001) - } - - @Test("fade saturates after one band") - func saturates() { - #expect(TaskComposerEdgeFadeScrollView.edgeAlpha(distance: 24) == 0) - #expect(TaskComposerEdgeFadeScrollView.edgeAlpha(distance: 500) == 0) - } -} From 05dc82ada9549a3ac10d3ce45f63d4bcd6d61d2a Mon Sep 17 00:00:00 2001 From: Abdulaziz Albahar <67667005+azooz2003-bit@users.noreply.github.com> Date: Mon, 21 Sep 2026 21:21:25 -0700 Subject: [PATCH 2/2] Preserve Files filter row height --- .../CmuxMobileShellUI/TerminalArtifactFilesSheet+Content.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TerminalArtifactFilesSheet+Content.swift b/Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TerminalArtifactFilesSheet+Content.swift index 4c3e081bf4bd..07662c7134b9 100644 --- a/Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TerminalArtifactFilesSheet+Content.swift +++ b/Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TerminalArtifactFilesSheet+Content.swift @@ -609,7 +609,7 @@ extension TerminalArtifactFilesSheet { ) .equatable() } - .frame(height: 44) + .frame(height: 34) .padding(.horizontal, 16) .padding(.vertical, 10) }