Skip to content

Fix iOS workspace toolbar leading glass chrome - #7110

Closed
azooz2003-bit wants to merge 2 commits into
mainfrom
fix-ios-toolbar-glass-leading
Closed

azooz2003-bit wants to merge 2 commits into
mainfrom
fix-ios-toolbar-glass-leading

Conversation

@azooz2003-bit

@azooz2003-bit azooz2003-bit commented Jun 30, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

  • keep the iOS workspace back button and title in one leading toolbar owner so SwiftUI cannot recenter or drop the title during transitions
  • render the back and title as separate compact glass islands with shared leading width math
  • update UI coverage to require a leading, separated title instead of accepting a centered principal title

Verification

  • ./ios/scripts/reload.sh --tag leadtb --no-launch
  • xcodebuild test -workspace ios/cmux.xcworkspace -scheme cmux-ios -destination 'id=C9D82663-886D-4EBD-92A0-96CF38A42FF3' -derivedDataPath /Users/abdulazizalbahar/Library/Developer/Xcode/DerivedData/cmux-ios-leadtb -only-testing:cmuxUITests/cmuxUITests/testInlineWorkspaceTitleMenuShowsWorkspaceActions
  • Simulator screenshots: /tmp/cmux-leadtb-inline-preview-final2-1s.png, /tmp/cmux-leadtb-inline-preview-final2-5s.png
  • Simulator video/contact sheet: /tmp/cmux-leadtb-toolbar-final.mov, /tmp/cmux-leadtb-toolbar-contact.png

Notes

  • The connected no-agent UI test was updated to assert leading toolbar chrome, but the local run hung before reaching toolbar setup in the existing mock-host attach path.

View with Codesmith Autofix with Codesmith
Need help on this PR? Tag /codesmith with what you need. Autofix is disabled.


Summary by cubic

Fixes the iOS workspace toolbar so the back button and title stay locked together on the left and remain stable during transitions. Switches title width to leading-based caps with a safety gap to prevent overlap with trailing controls.

  • Bug Fixes

    • Keep back button and title in one leading toolbar item to prevent principal recentering or title drops.
    • Apply leading width cap that reserves margin, back button, spacing, trailing cluster, and a safety gap so long titles truncate before the terminal picker/chat toggle.
    • Strengthened UI tests to require a leading, separated title aligned with the back button and clear of trailing controls.
  • Refactors

    • Added WorkspaceLeadingToolbarChrome and adopted it in workspace detail, chat pane, and the demo screen to own the leading cluster (back + title).
    • Rewrote MobileNavTitleWidth for leading rules with explicit leadingMargin, backButtonReserve, interControlSpacing, and trailingSafetyGap constants; updated unit tests accordingly.

Written for commit 5f5ceca. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features

    • Updated workspace top-bar layout to use a unified leading toolbar area for the back button, title, and actions.
    • Improved title sizing so the workspace header can expand until it would overlap other toolbar controls.
  • Bug Fixes

    • Refined iOS toolbar spacing and alignment for workspace, chat, and terminal views.
    • Adjusted UI tests to verify the new leading-and-separated toolbar behavior.

@vercel

vercel Bot commented Jun 30, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cmux Ready Ready Preview, Comment Jun 30, 2026 5:49pm
cmux-staging Building Building Preview, Comment Jun 30, 2026 5:49pm

@coderabbitai

coderabbitai Bot commented Jun 30, 2026 •

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

An error occurred during the review process. Please try again later.

📝 Walkthrough

Walkthrough

Introduces WorkspaceLeadingToolbarChrome, a generic ToolbarContent that unifies the optional back button and WorkspaceTitleMenu into a single .topBarLeading toolbar item. All workspace pane call sites (WorkspaceDetailView, WorkspaceChatPane, AgentChatDemoScreen) are migrated to use it. MobileNavTitleWidth.cap is updated from a symmetric centered reservation to an asymmetric leading+trailing model.

Changes

Leading Toolbar Chrome Refactor

Layer / File(s) Summary
MobileNavTitleWidth leading-layout cap math
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/MobileNavTitleWidth.swift, Packages/iOS/CmuxMobileShellUI/Tests/CmuxMobileShellUITests/MobileNavTitleWidthTests.swift
Replaces leadingReserve with leadingMargin, backButtonReserve, interControlSpacing, and trailingSafetyGap constants; changes cap computation from contentWidth - 2×max(leading,trailing) to contentWidth - leading - trailing - trailingSafetyGap; unit tests updated to assert the non-symmetric formula.
WorkspaceLeadingToolbarChrome new struct
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceLeadingToolbarChrome.swift
Adds WorkspaceLeadingToolbarChrome generic ToolbarContent that renders an HStack inside a .topBarLeading ToolbarItem, conditionally including WorkspaceBackButton and always including WorkspaceTitleMenu with wired layout/feature flags and view-builder closures.
Chrome adoption across workspace panes
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView.swift, WorkspaceChatPane.swift, AgentChatDemoScreen.swift, WorkspaceDetailView+DerivedState.swift
Removes per-pane conditional leading ToolbarItem back button and .principal WorkspaceTitleMenu composition; replaces all with WorkspaceLeadingToolbarChrome; deletes workspaceBackToolbarButton helpers; updates doc comments from "centered" to "leading".
UI tests updated to leading adjacency assertions
ios/cmuxUITests/cmuxUITests.swift
waitForWorkspaceTitleLeadingAndSeparated switches from window-center geometry to adjacency-gap checks (backButton.maxX to titleMenu.minX horizontal gap + midY alignment + trailing separation); call sites updated to use the leading helper and explicit local control variables.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • manaflow-ai/cmux#7056: Directly touches the same components — AgentChatDemoScreen, WorkspaceChatPane, WorkspaceDetailView, and MobileNavTitleWidth cap logic — in the same iOS leading toolbar refactor area.
  • manaflow-ai/cmux#7092: Modifies the same MobileNavTitleWidth cap constraints and workspace title toolbar wiring across the same pane files.
  • manaflow-ai/cmux#7097: Touches WorkspaceChatPane and WorkspaceDetailView toolbar composition with WorkspaceTitleMenu wiring, directly adjacent to this refactor.

Poem

🐰 A leading pill hops to the left,
No longer centered, no more bereft.
One chrome to rule the back and name,
The cap math changed — it's not the same.
leadingMargin + backButtonReserve,
This bunny's layout has so much nerve! 🎉

🚥 Pre-merge checks | ✅ 24 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 29.41% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (24 passed)
Check name Status Explanation
Title check ✅ Passed Short, specific, and accurately describes the leading toolbar chrome refactor.
Description check ✅ Passed Summary and verification are detailed and on-topic, though the demo video, review trigger, and checklist sections from the template are missing.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Cmux Swift Actor Isolation ✅ Passed Changes are limited to SwiftUI views and a pure width helper; no new @MainActor/Sendable issues or background-store access were introduced.
Cmux Swift Blocking Runtime ✅ Passed Diff only refactors toolbar/layout; no new blocking sync in production code. Added polling is confined to UI-test scaffolding, which the rule allows.
Cmux Browser Automation Off-Main ✅ Passed The diff only changes iOS workspace chrome/UI and tests; it doesn’t touch the browser socket routing files the rule covers, so no off-main browser-automation regression is introduced.
Cmux Expensive Synchronous Load ✅ Passed Diff only refactors toolbar chrome/title width UI; no synchronous agent-history load, JSON parsing, or main-actor disk scan was added or moved onto an interactive path.
Cmux Cache Substitution Correctness ✅ Passed Touched code only refactors leading-toolbar UI and width math; no persistence/history/undo/snapshot path swaps a fresh read for a cache.
Cmux No Hacky Sleeps ✅ Passed PASS: the PR only changes Swift UI/test sources and a SwiftUI helper; no TypeScript/JavaScript/shell/build-runtime code adds fixed sleeps, timers, or polling.
Cmux Algorithmic Complexity ✅ Passed Changed production code is constant-time SwiftUI layout/math only; no new nested scans, repeated filtering/sorting, or per-item rescans in the diff.
Cmux Swift Concurrency ✅ Passed The PR refactors SwiftUI toolbar chrome and tests; it doesn’t add new DispatchQueue/Combine/completion-handler patterns, and existing Task/XCTest callback usage stays at allowed UI/test boundaries.
Cmux Swift @Concurrent ✅ Passed Touched Swift changes are synchronous UI/layout code; no new @concurrent or nonisolated async work was added, and async tests stay on @MainActor.
Cmux Swift File And Package Boundaries ✅ Passed Changes are small UI/layout helpers in SwiftPM packages; the only large file touched (WorkspaceDetailView, 730 lines) got a narrow toolbar refactor, not a boundary-crossing expansion.
Cmux Swiftpm Lockfiles ✅ Passed The PR only changes Swift source/tests in CmuxMobileShellUI; no Package.swift, .gitignore, Xcode project, or Package.resolved files are in the diff.
Cmux Swift Logging ✅ Passed No added runtime logging; diff hunks contain no print/debugPrint/NSLog/Logger changes, and the only NSLog in a touched file is pre-existing and #if DEBUG-guarded.
Cmux User-Facing Error Privacy ✅ Passed Touched production files only refactor toolbar layout; no user-facing errors, alerts, or sensitive error payloads were added or changed.
Cmux Full Internationalization ✅ Passed No localization assets changed; new user-facing strings are either debug-only or already use L10n.localized APIs, and the production diff is layout-only.
Cmux Swiftui State Layout ✅ Passed Touched SwiftUI files use only top-level @State/@bindable and onGeometryChange callbacks; no new ObservableObject/@published, GeometryReader, or lazy/list-row store refs appear.
Cmux Architecture Rethink ✅ Passed The PR centralizes the leading toolbar in one owner and only adds layout/math/test updates—no sleeps, observers, side channels, or split lifecycle ownership.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed Diff only refactors toolbar chrome in allowed workspace views; no standalone NSWindow/WindowGroup code or cmuxAuxiliaryWindowIdentifiers changes were introduced.
Cmux Source Artifacts ✅ Passed Only source/docs/tests plus durable blog media were added; no temp/build/cache/checkouts or DerivedData paths appeared.
Cmux No Test Or Debug Seam In Production Source ✅ Passed Toolbar/UI refactor only; no new #if DEBUG test seam or *ForTesting accessor was added in production Sources.
Cmux No Ambient Global State ✅ Passed No new ambient-global-state surface was added; the new toolbar type is constructable, and added helpers are instance methods/static constants.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix-ios-toolbar-glass-leading

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@greptile-apps

greptile-apps Bot commented Jun 30, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes the iOS workspace toolbar by moving the back button and title into a single leading ToolbarItem via the new WorkspaceLeadingToolbarChrome struct, preventing SwiftUI from promoting the title into the centered principal slot during navigation transitions. The width math in MobileNavTitleWidth is updated from centered-symmetric math to asymmetric leading math, and all call sites in WorkspaceDetailView, WorkspaceChatPane, and AgentChatDemoScreen are migrated.

  • WorkspaceLeadingToolbarChrome: New ToolbarContent struct that owns both the back button (WorkspaceBackButton with .mobileGlassCompactToolbarControl()) and WorkspaceTitleMenu in one HStack inside a single .topBarLeading toolbar item, preventing SwiftUI from recentering or dropping the title.
  • MobileNavTitleWidth: Width cap switches from contentWidth - 2 * max(leading, trailing) to contentWidth - leading - trailing - trailingSafetyGap, adding explicit constants for leadingMargin, backButtonReserve, interControlSpacing, and trailingSafetyGap.
  • UI test: waitForWorkspaceTitleCenteredAndSeparated is renamed and rewritten to assert left-alignment (vertical midY alignment within 4pt, horizontal gap of 4–18pt between back button and title) instead of centering tolerance.

Confidence Score: 5/5

Safe to merge — the change is a targeted structural fix to a well-scoped iOS toolbar layout problem.

The new WorkspaceLeadingToolbarChrome struct correctly groups back button and title in one leading ToolbarItem, the updated MobileNavTitleWidth math is pure and fully covered by unit tests, and all three call sites are consistently migrated. The package targets iOS 18+ only, so platform-guard inconsistencies in WorkspaceDetailView are moot. The dead _ = app parameter and the earlier mobileGlassCompactToolbarIsland platform-guard issue are both covered by prior review threads and not introduced or worsened here.

No files require special attention. The ios/cmuxUITests/cmuxUITests.swift change is purely additive test hardening; the horizontalGapRange: 4...18 tolerance is generous but reasonable for the 8pt HStack spacing.

Important Files Changed

Filename Overview
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceLeadingToolbarChrome.swift New file: correctly wraps back button and title as a single leading ToolbarItem; glass styling and fixedSize applied appropriately.
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/MobileNavTitleWidth.swift Width math correctly updated from symmetric centered math to asymmetric leading math; new constants are well-named and documented.
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView.swift Both toolbar sites migrated to WorkspaceLeadingToolbarChrome; redundant workspaceBackToolbarButton computed property removed; no new issues.
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceChatPane.swift Correctly migrated to WorkspaceLeadingToolbarChrome; workspaceBackToolbarButton helper removed; mobileGlassCompactToolbarControl now applied in the chrome struct.
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/AgentChatDemoScreen.swift Demo screen migrated to WorkspaceLeadingToolbarChrome; back button now correctly receives glass styling it previously lacked.
Packages/iOS/CmuxMobileShellUI/Tests/CmuxMobileShellUITests/MobileNavTitleWidthTests.swift Tests updated to verify leading (asymmetric) math; all assertions correctly reference the new constants and expected values.
ios/cmuxUITests/cmuxUITests.swift Helper renamed and rewritten to check left-alignment instead of centering; dead _ = app statement left after removing window frame logic (flagged in a previous thread).
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView+DerivedState.swift Comment-only update to reflect leading toolbar chrome wording; no functional change.

Reviews (2): Last reviewed commit: "fix: keep iOS workspace toolbar leading" | Re-trigger Greptile

Comment on lines +96 to +106
/// Compact standalone glass-looking island for controls that must not be
/// coalesced by iOS toolbar glass grouping.
@ViewBuilder
func mobileGlassCompactToolbarIsland(minHeight: CGFloat = 44) -> some View {
self
.frame(minHeight: minHeight)
.padding(.horizontal, 14)
.padding(.vertical, 4)
.background(.ultraThinMaterial, in: Capsule())
.overlay(Capsule().stroke(.white.opacity(0.12), lineWidth: 1))
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 mobileGlassCompactToolbarIsland applies material on iOS 18 — breaks the existing platform-guard contract

Every sibling modifier in this file wraps iOS 26-specific visual behavior in #if os(iOS) + if #available(iOS 26.0, *) and explicitly returns self on iOS 18 (see mobileGlassCompactToolbarControl, mobileGlassCompactNavigationTitle). This new modifier applies .background(.ultraThinMaterial, in: Capsule()) and the stroke overlay unconditionally on all iOS versions. On iOS 18, both the back button (which previously got no backing via the now-removed .mobileGlassCompactToolbarControl() call) and the title pill (which previously used mobileGlassCompactNavigationTitle() — a no-op on iOS 18) will now display an explicit capsule-shaped material layer on top of the nav bar's own translucent material, producing double-layered translucency that no other toolbar control on that surface exhibits.

Suggested change
/// Compact standalone glass-looking island for controls that must not be
/// coalesced by iOS toolbar glass grouping.
@ViewBuilder
func mobileGlassCompactToolbarIsland(minHeight: CGFloat = 44) -> some View {
self
.frame(minHeight: minHeight)
.padding(.horizontal, 14)
.padding(.vertical, 4)
.background(.ultraThinMaterial, in: Capsule())
.overlay(Capsule().stroke(.white.opacity(0.12), lineWidth: 1))
}
/// Compact standalone glass-looking island for controls that must not be
/// coalesced by iOS toolbar glass grouping.
@ViewBuilder
func mobileGlassCompactToolbarIsland(minHeight: CGFloat = 44) -> some View {
#if os(iOS)
if #available(iOS 26.0, *) {
self
.frame(minHeight: minHeight)
.padding(.horizontal, 14)
.padding(.vertical, 4)
.background(.ultraThinMaterial, in: Capsule())
.overlay(Capsule().stroke(.white.opacity(0.12), lineWidth: 1))
} else {
self
}
#else
self
#endif
}

Comment thread ios/cmuxUITests/cmuxUITests.swift Outdated
Comment on lines +2160 to +2162
let deadline = Date().addingTimeInterval(timeout)
let window = app.windows.firstMatch
let windowFrame = window.exists ? window.frame : app.frame
let centerTolerance = max(windowFrame.width * 0.10, 28)
_ = app
let horizontalGapRange: ClosedRange<CGFloat> = 18...42

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Dead statement _ = app left after removing window-frame lookup

app was previously used to resolve window.frame for the center-tolerance check, but that whole block was removed. The parameter is no longer referenced in the function body, so _ = app is dead code suppressing an "unused parameter" warning. Either the app parameter should be dropped from the signature or _ = app should be replaced with a comment explaining why the parameter is intentionally kept for API stability.

Suggested change
let deadline = Date().addingTimeInterval(timeout)
let window = app.windows.firstMatch
let windowFrame = window.exists ? window.frame : app.frame
let centerTolerance = max(windowFrame.width * 0.10, 28)
_ = app
let horizontalGapRange: ClosedRange<CGFloat> = 18...42
let deadline = Date().addingTimeInterval(timeout)
let horizontalGapRange: ClosedRange<CGFloat> = 18...42

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceTitleMenu.swift`:
- Around line 21-26: The title width budget does not account for the horizontal
inset added by mobileGlassCompactToolbarIsland() in WorkspaceTitleMenu, so the
rendered pill can exceed the space reserved by MobileNavTitleWidth.cap. Fix this
by either reducing the cap by the island padding amount or moving the padding
inside the width-clamped fittedLabel so the final rendered width stays within
the intended budget.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 173e3b25-304e-40a1-a929-15ef8a6e11fb

📥 Commits

Reviewing files that changed from the base of the PR and between 8513010 and 680e899.

📒 Files selected for processing (11)
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/AgentChatDemoScreen.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/MobileNavTitleWidth.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceBackButton.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceChatPane.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView+DerivedState.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceLeadingToolbarChrome.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceTitleMenu.swift
  • Packages/iOS/CmuxMobileShellUI/Tests/CmuxMobileShellUITests/MobileNavTitleWidthTests.swift
  • Packages/iOS/CmuxMobileSupport/Sources/CmuxMobileSupport/View+MobileGlass.swift
  • ios/cmuxUITests/cmuxUITests.swift

Comment on lines +21 to +26
.buttonStyle(.plain)
.mobileGlassCompactToolbarIsland()
.accessibilityIdentifier("MobileWorkspaceTitleMenu")
} else {
fittedLabel
.mobileGlassCompactNavigationTitle()
.mobileGlassCompactToolbarIsland()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Reserve the island padding in the title-width budget.

fittedLabel is clamped to MobileNavTitleWidth.cap, then mobileGlassCompactToolbarIsland() adds 14pt of horizontal padding on each side. That makes the rendered title pill 28pt wider than the slot the new width model reserves, so narrow phones can still crowd the trailing controls after this refactor. Either subtract that inset in MobileNavTitleWidth.cap or move the padding inside the width-clamped label.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceTitleMenu.swift`
around lines 21 - 26, The title width budget does not account for the horizontal
inset added by mobileGlassCompactToolbarIsland() in WorkspaceTitleMenu, so the
rendered pill can exceed the space reserved by MobileNavTitleWidth.cap. Fix this
by either reducing the cap by the island padding amount or moving the padding
inside the width-clamped fittedLabel so the final rendered width stays within
the intended budget.

@azooz2003-bit

Copy link
Copy Markdown
Collaborator Author

Superseded by #7116 so the iOS toolbar fixes can be tested and reviewed in one PR.

This branch was successfully deployed

1 active deployment
Preview – cmux — 5f5cecaa Deployed Jun 30, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant