Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
105 changes: 0 additions & 105 deletions Sources/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7789,10 +7789,6 @@ struct VerticalTabsSidebar: View {
Spacer()
.frame(height: trafficLightPadding)

SidebarUpdateBanner(updateViewModel: updateViewModel)
.padding(.horizontal, 8)
.padding(.top, 8)

LazyVStack(spacing: tabRowSpacing) {
ForEach(Array(tabManager.tabs.enumerated()), id: \.element.id) { index, tab in
TabItemView(
Expand Down Expand Up @@ -8723,107 +8719,6 @@ private final class SidebarShortcutHintModifierMonitor: ObservableObject {
}
}

private struct SidebarUpdateBanner: View {
@ObservedObject var updateViewModel: UpdateViewModel

private var bannerVersion: String? {
if let detectedUpdateVersion = updateViewModel.detectedUpdateVersion {
return detectedUpdateVersion
}
if case .updateAvailable(let update) = updateViewModel.effectiveState {
return UpdateViewModel.normalizedDetectedUpdateVersion(from: update.appcastItem.displayVersionString)
}
return nil
}

private var titleText: String {
guard let bannerVersion else {
return String(localized: "update.available.short", defaultValue: "Update Available")
}
return String(localized: "update.available.withVersion", defaultValue: "Update Available: \(bannerVersion)")
}

private var messageText: String {
if case .updateAvailable = updateViewModel.effectiveState {
let message = updateViewModel.description
if !message.isEmpty {
return message
}
}
return String(localized: "update.downloadAndInstall", defaultValue: "Download and install the latest version")
}

private var actionDisabled: Bool {
switch updateViewModel.effectiveState {
case .checking, .downloading, .extracting, .installing:
return true
default:
return false
}
}

var body: some View {
if bannerVersion != nil {
VStack(alignment: .leading, spacing: 10) {
HStack(alignment: .top, spacing: 10) {
Image(systemName: "shippingbox.fill")
.font(.system(size: 13, weight: .semibold))
.foregroundStyle(cmuxAccentColor())
.padding(.top, 1)

VStack(alignment: .leading, spacing: 4) {
Text(titleText)
.font(.system(size: 12, weight: .semibold))
.foregroundColor(.primary)
.accessibilityIdentifier("SidebarUpdateBannerTitle")
Text(messageText)
.font(.system(size: 11))
.foregroundColor(.secondary)
.fixedSize(horizontal: false, vertical: true)
}

Spacer(minLength: 0)
}

HStack {
Spacer(minLength: 0)
Button(String(localized: "common.installAndRelaunch", defaultValue: "Install and Relaunch")) {
installDetectedUpdate()
}
.buttonStyle(.borderedProminent)
.controlSize(.small)
.disabled(actionDisabled)
.accessibilityIdentifier("SidebarUpdateBannerAction")
}
}
.padding(12)
.frame(maxWidth: .infinity, alignment: .leading)
.background(
RoundedRectangle(cornerRadius: 12, style: .continuous)
.fill(cmuxAccentColor().opacity(0.12))
)
.overlay(
RoundedRectangle(cornerRadius: 12, style: .continuous)
.stroke(cmuxAccentColor().opacity(0.28), lineWidth: 1)
)
.contentShape(RoundedRectangle(cornerRadius: 12, style: .continuous))
.accessibilityIdentifier("SidebarUpdateBanner")
}
}

private func installDetectedUpdate() {
if case .updateAvailable(let update) = updateViewModel.effectiveState {
update.reply(.install)
return
}
if updateViewModel.effectiveState.isInstallable {
updateViewModel.effectiveState.confirm()
return
}
AppDelegate.shared?.attemptUpdate(nil)
}
}

private struct SidebarFooter: View {
@ObservedObject var updateViewModel: UpdateViewModel
let onSendFeedback: () -> Void
Expand Down
12 changes: 8 additions & 4 deletions cmuxUITests/UpdatePillUITests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -124,17 +124,21 @@ final class UpdatePillUITests: XCTestCase {
assertVisibleSize(noUpdatePill)
}

func testSidebarUpdateBannerShowsForBackgroundDetectedUpdate() {
func testBackgroundDetectedUpdateKeepsOnlyBottomUpdatePill() {
let systemSettings = XCUIApplication(bundleIdentifier: "com.apple.systempreferences")
systemSettings.terminate()
let app = XCUIApplication()
app.launchEnvironment["CMUX_UI_TEST_MODE"] = "1"
app.launchEnvironment["CMUX_UI_TEST_DETECTED_UPDATE_VERSION"] = "9.9.9"
app.launchEnvironment["CMUX_UI_TEST_UPDATE_STATE"] = "available"
app.launchEnvironment["CMUX_UI_TEST_UPDATE_VERSION"] = "9.9.9"
Comment on lines +133 to +134

@cubic-dev-ai cubic-dev-ai Bot Mar 17, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Forcing CMUX_UI_TEST_UPDATE_STATE makes the pill appear regardless of the background-detected update signal, so this test no longer verifies that background detection drives the footer pill. It will pass even if the detected-update path is broken.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At cmuxUITests/UpdatePillUITests.swift, line 133:

<comment>Forcing `CMUX_UI_TEST_UPDATE_STATE` makes the pill appear regardless of the background-detected update signal, so this test no longer verifies that background detection drives the footer pill. It will pass even if the detected-update path is broken.</comment>

<file context>
@@ -124,17 +124,21 @@ final class UpdatePillUITests: XCTestCase {
         let app = XCUIApplication()
         app.launchEnvironment["CMUX_UI_TEST_MODE"] = "1"
         app.launchEnvironment["CMUX_UI_TEST_DETECTED_UPDATE_VERSION"] = "9.9.9"
+        app.launchEnvironment["CMUX_UI_TEST_UPDATE_STATE"] = "available"
+        app.launchEnvironment["CMUX_UI_TEST_UPDATE_VERSION"] = "9.9.9"
         launchAndActivate(app)
</file context>
Suggested change
app.launchEnvironment["CMUX_UI_TEST_UPDATE_STATE"] = "available"
app.launchEnvironment["CMUX_UI_TEST_UPDATE_VERSION"] = "9.9.9"
// Leave CMUX_UI_TEST_UPDATE_STATE unset so detected updates drive the pill.
Fix with Cubic

launchAndActivate(app)

XCTAssertTrue(app.otherElements["SidebarUpdateBanner"].waitForExistence(timeout: 6.0))
XCTAssertTrue(app.staticTexts["Update Available: 9.9.9"].waitForExistence(timeout: 2.0))
XCTAssertTrue(app.buttons["SidebarUpdateBannerAction"].waitForExistence(timeout: 2.0))
let pill = pillButton(app: app, expectedLabel: "Update Available: 9.9.9")
XCTAssertTrue(pill.waitForExistence(timeout: 6.0))
assertVisibleSize(pill)
XCTAssertFalse(app.otherElements["SidebarUpdateBanner"].exists)
XCTAssertFalse(app.buttons["SidebarUpdateBannerAction"].exists)
}

func testNoSparklePermissionDialogIsShown() {
Expand Down
Loading