diff --git a/Sources/Cloud/CloudTeamPickerHeader.swift b/Sources/Cloud/CloudTeamPickerHeader.swift index ae129d1f346f..e69b660c56f8 100644 --- a/Sources/Cloud/CloudTeamPickerHeader.swift +++ b/Sources/Cloud/CloudTeamPickerHeader.swift @@ -68,7 +68,10 @@ struct CloudTeamPickerHeader: View { } } Spacer(minLength: 0) - agentMenu() + // Invite is the only action surfaced in the Cloud sidebar header. + // Machine creation, refresh, and the agent menu remain available + // through their command and menu entry points without competing + // with the team invite affordance here. if let accountFlow, accountFlow.confirmedTeamID != nil { MachinesChromeLabelButton( symbolName: "person.badge.plus", @@ -81,25 +84,6 @@ struct CloudTeamPickerHeader: View { } .accessibilityIdentifier("CloudTeamInviteButton") } - switch actions { - case .inline: - MachinesChromeIconButton( - symbolName: "arrow.clockwise", - accessibilityLabel: refreshLabel, - isBusy: isRefreshing, - action: onRefresh - ) - .accessibilityIdentifier("CloudHeaderRefreshButton") - MachinesChromeIconButton( - symbolName: "plus", - accessibilityLabel: newMachineLabel, - isBusy: false, - action: onNewMachine - ) - .accessibilityIdentifier("CloudHeaderNewMachineButton") - case .overflowMenu: - machineActionsMenu - } } } diff --git a/Sources/Cloud/MachinesPanelView.swift b/Sources/Cloud/MachinesPanelView.swift index 9de53523bd9e..0414863aac1d 100644 --- a/Sources/Cloud/MachinesPanelView.swift +++ b/Sources/Cloud/MachinesPanelView.swift @@ -224,7 +224,16 @@ struct MachinesPanelView: View { } else if isTeamChangePending { teamScopeLoading } else { - content + VStack(spacing: 0) { + HStack { + Spacer(minLength: 0) + cloudAgentMenu + } + .padding(.horizontal, RightSidebarChromeMetrics.barHorizontalPadding) + .padding(.vertical, 2) + .accessibilityIdentifier("CloudMachinesAgentActionsRow") + content + } } } diff --git a/cmuxTests/CloudMachinesHeaderCountTests.swift b/cmuxTests/CloudMachinesHeaderCountTests.swift index 7cae725b8cbf..2a1cfaf4005e 100644 --- a/cmuxTests/CloudMachinesHeaderCountTests.swift +++ b/cmuxTests/CloudMachinesHeaderCountTests.swift @@ -39,9 +39,9 @@ struct CloudMachinesHeaderCountTests { #expect(CloudTreeRowContentView.groupCount(for: .cloudMachinesSection(canCreateMachine: true)) == nil) } - // `ViewThatFits` shows the inline row only when its ideal width fits the - // bar, and falls back to the overflow menu otherwise. Measuring that ideal - // width needs no accessibility client, unlike reading SwiftUI's tree. + // The header still measures both team-name layout candidates. Measuring + // their ideal widths needs no accessibility client, unlike reading the + // SwiftUI tree. @Test("Narrow Cloud headers move machine actions into one overflow menu") func narrowHeaderCollapsesMachineActions() async throws { let inline = try await idealRowWidth(.inline, teamName: Self.longTeamName) @@ -49,14 +49,14 @@ struct CloudMachinesHeaderCountTests { "The inline row (\(inline)pt) fits a 220pt sidebar, so the overflow menu never shows") } - @Test("A wide Cloud header keeps refresh and new machine buttons inline") - func wideHeaderKeepsMachineActionsInline() async throws { + @Test("A wide Cloud header keeps its action row stable") + func wideHeaderKeepsActionRowStable() async throws { let inline = try await idealRowWidth(.inline, teamName: "Team A") #expect(inline <= Self.barContentWidth(420), - "The inline row (\(inline)pt) overflows a 420pt sidebar, so Refresh and New Machine fold away") - // The overflow row trades two buttons for one menu, so it never needs more room. + "The header action row (\(inline)pt) overflows a 420pt sidebar") let overflow = try await idealRowWidth(.overflowMenu, teamName: "Team A") - #expect(overflow < inline) + #expect(overflow == inline, + "The Invite-only header should not change width between layout candidates") } @Test("A free plan at its limit turns orange and names the upgrade", arguments: [