Skip to content

cloud sidebar polish: header refresh, tab switch, empty states, errors and upgrade - #17074

Merged
austinywang merged 81 commits into
mainfrom
cloud-sidebar-polish
Oct 5, 2026
Merged

austinywang merged 81 commits into
mainfrom
cloud-sidebar-polish

Conversation

@lucasr1b

@lucasr1b lucasr1b commented Oct 3, 2026 •

Copy link
Copy Markdown
Collaborator

stacked on #17070 (base cloud-machine-reorder), so this diff is only the polish on top of the reorder. it also merges #16812 (cloud-sidebar-connecting: only the ports status button acts, its hover chip, link tabs hidden while connecting), so those lines show here until #16812 lands.

Summary

cloud tab

  • refresh moves from the bottom of the panel to a small icon right after the cloud machines count ("5/5") and after my devices, in the count's grey, with the same hover fill as + and ⋯. it spins only for a refresh someone asked for, not the 45 s poll. the header still opens and closes from anywhere but the icon
  • an empty fleet keeps a "No cloud machines yet" line, so cloud machines keeps its chevron. my devices (and settings > computers) say "No other devices yet" and "Discover other devices"
  • a failed create's row is just its notice: the warning and "Couldn't create machine" on one line, without the machine's name. the name and reason stay in its tooltip and on the create's page
  • a create refused at the plan's machine limit (vm_active_limit_exceeded) offers Upgrade Plan on its page, ahead of retry and dismiss, when a bigger plan exists (not on max, team or founders)
  • row selection is a layer like the hover fill, so highlighted rows no longer flicker while the sidebar resizes

mode tabs

  • switching tabs eases the two tabs' widths on one short curve with no overshoot. nothing travels across the bar: the old tab's fill fades as it narrows and the new one's as it opens. labels are uncovered by their slot with a soft edge instead of re-truncating every frame, so a partly shown label fades out rather than ending in an ellipsis. reduce motion makes it instant
  • an icon-only tab centers its icon in its highlight
  • the open-as-pane button stays in the bar in every tab. dock, feed and custom can't open as panes yet, so there it beeps and its tooltip says so
  • the right sidebar's minimum and opening width fit the selected tab's full name with every other tab as its icon, plus the pane and close buttons (icon icon [icon name] icon icon). it uses the widest name, so the width doesn't change as the selection moves, follows the tabs shown and the font size, and never drops below 295 pt
Before (Cloud sidebar: Nightly (prod account) vs this branch (dev account)) After (Cloud sidebar: Nightly (prod account) vs this branch (dev account))
Before After

Showcase: tab switching and the Cloud sidebar

Showcase

Testing

  • updated: CloudTreeCategoryCreateActionTests (empty fleet keeps its line), CloudTreeHeaderActionsTests.sectionRefresh
  • new: CloudSidebarPolishTests (machine-limit upgrade and plan ladder, one-label bar width, content minimum width)
  • app tests run in ci; scripts/verify-local.py and scripts/localization_catalog.py check clean
  • builds and launches in a tagged dev build against the dev backend; not dogfooded yet
  • localization: new machines.empty.none, machines.pending.upgrade, rightSidebar.openAsPane.unavailable; changed devices.empty.title, devices.discovery.toggle, devices.discovery.stop, devices.discovery.settingsDisabled. all 9 macOS locales

Changelog

Changed: Cloud sections refresh from their headers, right sidebar tabs switch smoothly and always fit the open tab's name, and a create refused at the machine limit offers an upgrade

Tracking issue: #17110

Summary by CodeRabbit

  • New Features

    • Cloud sidebar machine rows can now be dragged and reordered with animated feedback, including within the Cloud Machines section.
    • Added refresh controls for Cloud Machines and Devices, with progress indicators while lists update.
    • Failed machine creation due to plan limits can now offer an Upgrade Plan action when a higher plan may be available.
    • Right-sidebar tabs can be reordered by dragging, with the new order retained. Tab labels fade or hide when space is limited, and sidebar sizing adapts to the selected tab.
    • Connecting machines no longer show detail tabs before fleet information is available.
  • Bug Fixes

    • Ports status rows no longer trigger refresh or wake actions when clicked.
    • Updated device discovery and empty-state wording to refer to “devices” rather than “Macs.”

…ecting row up with new workspace, hover on the ports action

- while a machine connects, ports, terminals and displays have nothing current, so the tab row keeps only resources (fleet telemetry)
- the connecting spinner takes new workspace's chevron column
- the ports status action (refresh, set up vpn) is a quiet rounded chip with the tree's fills; its title brightens and fill deepens on hover and press
the live cloud tab lists machines under the cloud machines section, and
dropping one there is refused today. this fails until the fix.
…he section headers

machine drags under the cloud machines section were refused because the
drop only looked at top-level rows; drops and menu moves now find the
machines wherever they sit.

the real machine row is the drag visual: open machines close for the
drag, peers spring aside against frozen geometry, and the drop lands
every row from where it is on screen. mode tabs drag the same way and
hand off to a native drag when pulled out of the bar, so they still open
as panes. switching tabs slides one selection highlight, and an icon-only
tab centers its icon. refresh moves from the bottom of the panel to the
cloud machines and my devices headers.
…up pr

keeps this pr to machine and tab reorder. the refresh icons in the
cloud machines and my devices headers, the sliding tab highlight and the
centered tab icons come back in the stacked cloud sidebar polish pr.
…light, centered tab icons

refresh moves from the bottom of the cloud panel to an icon left of the
cloud machines + and the my devices menu, with the same hover and a
spinner that stays on screen while it runs. switching tabs slides one
selection highlight while the widths re-share, and an icon-only tab
centers its icon.
@coderabbitai

coderabbitai Bot commented Oct 3, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

Important

Review skipped

We couldn't safely recover the incremental review. No full review was started, and the last reviewed checkpoint was preserved. Retry later, or explicitly request a full review by commenting @coderabbitai full review.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This pull request updates Cloud tree refresh and machine reordering, right-sidebar mode-bar interactions and sizing, machine-creation upgrade actions, device terminology, and macOS CI build-root resolution. It also changes two public utility types from enums to non-instantiable structs.

Changes

Canonical CI root resolution

Layer / File(s) Summary
Resolve and export the build root
scripts/ci/resolve-canonical-root.py, tests/test_ci_resolve_canonical_root.py, .github/workflows/ci-macos.yml
The workflow obtains and exports its canonical root through the resolver. Tests cover root selection, invalid inputs, and workflow ordering.

Cloud tree refresh and machine ordering

Layer / File(s) Summary
Add refresh actions to section headers
Packages/macOS/CmuxCloud/Sources/CmuxCloud/Tree/CloudTreeSectionRefresh.swift, Sources/Cloud/CloudTreeSectionRefreshHeader.swift, Sources/Cloud/CloudTreeRowContentView.swift, Sources/Cloud/CloudTreeCellView.swift, Sources/Cloud/MachinesPanelView*, Sources/Cloud/CloudTreeBuildInputs.swift, Sources/Cloud/CloudTreeNode*, Sources/Cloud/CloudTreeOutlineView.swift, cmuxTests/CloudTreeHeaderActionsTests.swift, cmux.xcodeproj/project.pbxproj
Refresh state reaches Cloud Machines and Devices section headers. The headers show progress while refreshing and expose clickable refresh actions.
Update Cloud tree row states and actions
Packages/macOS/CmuxCloud/Sources/CmuxCloud/Tree/CloudTreePlaceholder.swift, Sources/Cloud/CloudTree*, Sources/Cloud/CloudPortsStatusContent.swift, cmuxTests/CloudTreeCategoryCreateActionTests.swift, cmuxTests/CloudTreeMachineDetailLayoutTests.swift, cmuxTests/CloudPortsVPNAffordanceTests.swift, cmux.xcodeproj/project.pbxproj
Empty fleets show an informational placeholder. Connecting and failed machine rows use updated layouts, and opening Ports status rows does not invoke their refresh or wake actions. Row selection rendering and the Ports status button styling also change.
Calculate Cloud machine reorder placement
Packages/macOS/CmuxCloud/Sources/CmuxCloud/Tree/CloudTreeReorderLiftLayout.swift, Packages/macOS/CmuxCloud/Tests/CmuxCloudTests/CloudTreeReorderLiftLayoutTests.swift
The layout calculates peer slots and row offsets for machine drags, with bounded overscroll. Tests cover drag thresholds, expanded rows, fixed blocks, and invalid layouts.
Lift and reorder Cloud machine rows
Sources/Cloud/Sidebar/*, Sources/Cloud/CloudTreeNSOutlineView.swift, Sources/Cloud/CloudTreeOutlineView+MachineDetailTabs.swift, Sources/Cloud/CloudTreeOutlineView+MachineLookup.swift, Sources/Cloud/CloudTreeOutlineView.swift, cmuxTests/CloudMachineOrdering*, cmux.xcodeproj/project.pbxproj
Machine drags use pointer-derived slots and resolved sibling groups. Expanded sibling machines close during the drag and are restored when it finishes. Tests cover sectioned rows, drag completion, and cancellation.

Machine-limit upgrade action

Layer / File(s) Summary
Determine upgrade eligibility and present the action
Packages/macOS/CmuxCloud/Sources/CmuxCloud/Billing/ProUpgradeSource.swift, Packages/macOS/CmuxCloud/Sources/CmuxCloud/Machines/MachinePlanSnapshot.swift, Sources/Cloud/MachineCreateOperation.swift, Sources/Panels/MachineCreateLoadingContent.swift, cmuxTests/CloudSidebarPolishTests.swift
Machine creation detects active-machine-limit failures. The loading view offers an Upgrade Plan action when the plan data is unavailable or a higher plan exists.

Right sidebar mode bar

Layer / File(s) Summary
Measure tabs and report the required width
Sources/RightSidebarModeBarTabWidths.swift, Sources/RightSidebarModeBarTabsLayout.swift, Sources/RightSidebarModeBarWidthReport.swift, Sources/ModeBarLabelEdgeFade.swift, Sources/RightSidebarChromeStyle.swift, Sources/FileExplorerState.swift, Sources/ContentView.swift, Sources/RightSidebarPanelView.swift, cmuxTests/CloudSidebarPolishTests.swift, cmux.xcodeproj/project.pbxproj
The mode bar reports its required width, and sidebar clamping uses that value. Tab labels fade or hide as their available width changes.
Drag tabs and hand off pane-capable modes
Packages/macOS/CmuxSidebar/Sources/CmuxSidebar/RightSidebarModeBarDragLayout.swift, Sources/RightSidebarModeBarDrag.swift, Sources/RightSidebarModeDragPayload.swift, Sources/RightSidebarPanelView.swift, cmuxTests/RightSidebarTabCustomizationTests.swift, cmux.xcodeproj/project.pbxproj
Tabs reorder from drag geometry and pointer movement. A pane-capable mode can start a native pane drag when it leaves the bar.

Device discoverability terminology

Layer / File(s) Summary
Update discovery and empty-state wording
Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Models/DeviceAccessControl.swift, Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Navigation/CuratedSettingEntry+Devices.swift, Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Resources/Localizable.xcstrings, Packages/macOS/CmuxSettingsUI/Sources/CmuxSettingsUI/Sections/ComputersSection.swift, Sources/Cloud/CloudTreeDevicesEmptyView.swift, Sources/Cloud/CloudTreeNode.swift, Sources/Cloud/MachinesPanelView.swift, Sources/Hive/HiveComputersService.swift, Packages/macOS/CmuxSettingsUI/Tests/CmuxSettingsUITests/DeviceAccessControlTests.swift, cmuxTests/DeviceDiscoverabilityGatingTests.swift, cmuxUITests/*
Discovery controls and empty-state messages use “devices” instead of “Macs.” Localization and test expectations use the updated wording.

Static-only public API types

Layer / File(s) Summary
Restrict construction of static API types
Packages/macOS/CmuxBrowser/Sources/CmuxBrowser/Scripting/ReactGrabBridgeScripts.swift, Packages/macOS/CmuxControlSocket/Sources/CmuxControlSocket/Wire/SocketCommandLine.swift
Both public types become structs with private initializers. Their static APIs remain available.

Priority: ➖ Normal

Estimated code review effort: 5 (Critical) | ~90 minutes

Change: Feature

Sequence Diagram(s)

sequenceDiagram
  participant MachinesPanelView
  participant CloudTreeOutlineView
  participant CloudTreeSectionRefreshHeader
  participant CloudTreeNodeActions
  MachinesPanelView->>CloudTreeOutlineView: Pass cloud-machine and device refresh state
  CloudTreeOutlineView->>CloudTreeSectionRefreshHeader: Render section refresh state and action
  CloudTreeSectionRefreshHeader->>CloudTreeNodeActions: Invoke refresh action on click
Loading
sequenceDiagram
  participant CloudTreeOutlineView
  participant CloudTreeMachineReorderLift
  participant CloudMachineReorderScope
  participant CloudMachineReorderDrop
  CloudTreeOutlineView->>CloudTreeMachineReorderLift: Begin lift for eligible machine drag
  CloudTreeMachineReorderLift->>CloudMachineReorderScope: Resolve reorderable siblings
  CloudTreeMachineReorderLift->>CloudTreeOutlineView: Report pointer-derived slot
  CloudTreeOutlineView->>CloudMachineReorderDrop: Build drop from source and slot
  CloudTreeOutlineView->>CloudTreeMachineReorderLift: Finish lift with optional commit
Loading
sequenceDiagram
  participant RightSidebarModeBarTabDrag
  participant RightSidebarModeBarDragController
  participant RightSidebarModePaneDragSource
  RightSidebarModeBarTabDrag->>RightSidebarModeBarDragController: Update drag position and target slot
  RightSidebarModeBarDragController->>RightSidebarModeBarDragController: Commit reordered modes on in-bar release
  RightSidebarModeBarDragController->>RightSidebarModePaneDragSource: Start native drag when a pane-capable mode leaves the bar
  RightSidebarModePaneDragSource->>RightSidebarModePaneDragSource: End registration when native drag finishes
Loading

Suggested reviewers: austinywang, teamleaderleo

Merge Risk: 🟡 Moderate · up to bd6be

Dragging right-sidebar tabs can save them in the wrong order when the visible tabs differ from the saved list. In narrow windows, the sidebar can also grow past its configured maximum and squeeze the terminal. Fix the reorder before merging.

Security Architecture Review

Security architecture risk: 🔵 Low · up to bd6be

Build-directory selection now uses a validated runner-identity fallback before cleanup. The checked controls limit accepted paths, but filesystem ownership and concurrent-slot isolation on persistent machines remain unconfirmed. No newly exploitable security path was demonstrated.

Retained concerns
No architecture-level concerns identified.

Security review details

Security Blast Radius

  • inferred — The directly demonstrated sensitive scope is the selected runner’s DerivedData and CAS directories and matching stale copies, operated on with the job’s filesystem privileges. Broader exposure would depend on filesystem redirection or shared-runner access that the available evidence does not establish.

Trust Boundaries and Controls

  • observed — CI accepts only the fixed canonical root or a one- or two-digit suffix; the workflow additionally restricts suffixed roots to an owned-runner product identity. These are string-level controls. The inspected admission path does not prove filesystem ownership, non-symlink parent integrity or uniqueness of concurrent runner slots.

Resilience and Maintainability Implications

  • observed — The machine-drag mouse-up fallback checks native session identity and sequence before invoking the terminal handler. Terminal processing rejects mismatched active sessions, while lift completion invalidates its display link and clears mutation ownership before executing the move.

Hardening Proposals

  • proposed — Establish runner-side evidence for protected root ownership, non-symlink parent directories, unique concurrent-slot assignment and abandoned-root cleanup. If those guarantees are not enforced by runner provisioning, enforce them at the admission boundary rather than relying on lexical validation alone.

Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

❌ Failed checks (5 errors, 1 inconclusive)

Check name Status Explanation Resolution
Cmux Swift Blocking Runtime ❌ Error The PR adds a production polling loop in Sources/Cloud/Sidebar/CloudTreeMachineReorderLift.swift. begin creates and schedules a CADisplayLink on the main run loop in .common mode; each `tick()… Remove the CADisplayLink polling and update the lift from AppKit's drag-location callbacks, such as the existing drag update path that receives NSDraggingInfo. Keep row glide and landing effects as Core Animation animations. Use animati…
Cmux Algorithmic Complexity ❌ Error The new drag layout filters the scalable machine list on every display-link frame. CloudTreeMachineReorderLift.tick() calls update() repeatedly, and `CloudTreeReorderLiftLayout.placement(dragOffse… Precompute the stable peer indices and row-to-block mapping when CloudTreeReorderLiftLayout is initialized. Avoid filtering and rebuilding full-span collections on each display-link tick. If per-frame full-span work remains, add measureme…
Cmux Swift Concurrency ❌ Error The diff adds two @Published app-state properties: FileExplorerState.modeBarMinimumWidth and MachinesPanelViewModel.isRefreshingOnRequest. Both classes already used ObservableObject, but these… Replace the newly added Combine state with Observation-based state. Make the affected models @Observable and update their SwiftUI ownership and observation (@State/@Bindable or equivalent); keep the new values as ordinary stored prope…
Cmux Swift Package Boundaries ❌ Error The diff adds MachineCreateOperation.hitMachineLimit in the app target. It identifies the service error by searching failure output for vm_active_limit_exceeded, and `MachineCreateLoadingContent.o… Move the active-machine-limit error classification into the CmuxCloudMachines package, preferably as a computed property on CloudMachineCreateOperation (or a small package-level failure type). Make the app’s MachineCreateOperation del…
Cmux Swiftui State Layout ❌ Error The diff adds new @Published SwiftUI state and writes state from the layout pass. Sources/FileExplorerState.swift adds @Published modeBarMinimumWidth to FileExplorerState: ObservableObject; `S… Move the new SwiftUI-owned state to @Observable models owned with @State, or pass immutable snapshots and action closures rather than adding new @Published properties to the legacy ObservableObject models. Remove the state update fr…
Docstring Coverage ❓ Inconclusive Docstring coverage is 44.03% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 134 functions across 49 files. (26 skippe… Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (19 passed)
Check name Status Explanation
Title check ✅ Passed The title identifies the Cloud sidebar polish and names several main changes. It is broad, but it clearly relates to the pull request.
Description check ✅ Passed The description includes a summary, testing details, a changelog entry, and visual examples. It also reports the localization audit. The checklist is omitted, and the CI test lane is not named, but th…
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 Cloud Persistent Session And Early Input ✅ Passed PASS. The reviewed diff does not change Cloud terminal creation, cmux-tui transport, terminal attachment, authentication, revision, lease, or input-routing code. The only nearby terminal-related chang…
Cmux Swift Actor Isolation ✅ Passed No Swift 6 actor-isolation failure is introduced. The new Cloud value models and drag-layout helpers are pure values; CmuxCloud builds in Swift 5 mode, and CmuxSidebar builds in Swift 6 mode without a…
Cmux Browser Automation Off-Main ✅ Passed The PR does not introduce or move browser socket automation commands. The authoritative diff does not change Sources/TerminalController.swift or ControlCommandExecutionPolicy.swift, which the rule…
Cmux Expensive Synchronous Load ✅ Passed No changed production Swift code adds or moves a synchronous agent-history load onto a main-actor or interactive path. The reviewed additions concern Cloud sidebar refresh, machine-row drag behavior, …
Cmux Cache Substitution Correctness ✅ Passed No qualifying cache substitution is introduced. The plan cache is used only to show the transient Upgrade Plan affordance in MachineCreateLoadingContent; it does not supply persisted, history, undo,…
Cmux No Hacky Sleeps ✅ Passed No prohibited delay was introduced in the scoped non-Swift code. The only changed eligible runtime script is scripts/ci/resolve-canonical-root.py; it performs environment validation and deterministi…
Cmux Swift @Concurrent ✅ Passed PASS. The reviewed Swift diff adds no nonisolated async function or @concurrent annotation, and it introduces no CPU-, file-, or network-heavy async helper called from UI isolation. New drag and r…
Cmux Swiftpm Lockfiles ✅ Passed The PR changes no Package.swift, Package.resolved, or .gitignore files. The cmux.xcodeproj/project.pbxproj diff adds and removes source and test file references; it does not change SwiftPM pac…
Cmux Swift Logging ✅ Passed PASS. The review-scoped diff adds no production Swift print, debugPrint, dump, or NSLog calls; no file-scoped Logger declarations; and no ad hoc file or stdout/stderr diagnostics. The only S…
Cmux User-Facing Error Privacy ✅ Passed No changed user-facing text exposes the prohibited implementation details. The new end-user copy is generic: “Upgrade Plan,” “No cloud machines yet,” “No other devices yet,” and the pane-unavailable m…
Cmux Full Internationalization ✅ Passed The PR routes changed and new user-facing Swift text through localized APIs. The changed and new entries in Resources/Localizable.xcstrings and the changed entry in the CmuxSettingsUI catalog have n…
Cmux Architecture Rethink ✅ Passed The diff does not introduce a prohibited symptom patch. The new mouse-up monitors are documented fallbacks and route through the existing drag-end handlers; the mode-bar drag controller owns and remov…
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed The diff adds or materially changes no standalone cmux-owned window. Sources/RightSidebarModeDragPayload.swift starts a native drag session for carrying a sidebar tab into a pane; it does not create…
Cmux Source Artifacts ✅ Passed The reviewed diff changes 77 paths. The added paths are Swift product source and tests, plus a Python CI resolver and its tests. The remaining changes are product code, CI configuration, project regis…
Cmux No Test Or Debug Seam In Production Source ✅ Passed The PR adds no test-build-guarded state accessors or members with the rule’s test/debug naming patterns. The only private-to-internal change is disclosureLeading(atRow:) in `Sources/Cloud/CloudTreeN…
Full details: Docstring Coverage

Explanation

Docstring coverage is 44.03% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 134 functions across 49 files. (26 skipped: 3 unsupported, 1 too large, 22 over the file limit.)

Full details: Cmux Swift Blocking Runtime

Explanation

The PR adds a production polling loop in Sources/Cloud/Sidebar/CloudTreeMachineReorderLift.swift. begin creates and schedules a CADisplayLink on the main run loop in .common mode; each tick() samples NSEvent.mouseLocation and updates the lifted rows. The display link is new in the reviewed diff and remains active for the drag until finish or discard invalidates it. This is timer-based polling in shipped Swift code, which the check flags by default.

Resolution

Remove the CADisplayLink polling and update the lift from AppKit's drag-location callbacks, such as the existing drag update path that receives NSDraggingInfo. Keep row glide and landing effects as Core Animation animations. Use animation completion callbacks for animation-dependent cleanup, and invalidate no recurring timer because none should be created.

Full details: Cmux Algorithmic Complexity

Explanation

The new drag layout filters the scalable machine list on every display-link frame. CloudTreeMachineReorderLift.tick() calls update() repeatedly, and CloudTreeReorderLiftLayout.placement(dragOffset:) rebuilds order with span.indices.filter at line 89. It also rebuilds offset dictionaries for the span on each call. This is O(M + R) per frame for M machine blocks and R rows. The plan model allows uncapped paid fleets, so the code states no size bound. The review rules flag repeated collection filtering in hot UI paths. No benchmark or profiling note for this path was found.

Resolution

Precompute the stable peer indices and row-to-block mapping when CloudTreeReorderLiftLayout is initialized. Avoid filtering and rebuilding full-span collections on each display-link tick. If per-frame full-span work remains, add measurements that show it stays within the UI frame budget at about 1,000 machine records.

Full details: Cmux Swift Concurrency

Explanation

The diff adds two @Published app-state properties: FileExplorerState.modeBarMinimumWidth and MachinesPanelViewModel.isRefreshingOnRequest. Both classes already used ObservableObject, but these additions expand Combine-based state. The repository already uses @Observable, so the rule’s condition for new Combine app state applies. The added DispatchQueue.main.async in RightSidebarModeBarWidthReport defers a SwiftUI layout update; it is not the basis for this failure.

Resolution

Replace the newly added Combine state with Observation-based state. Make the affected models @Observable and update their SwiftUI ownership and observation (@State/@Bindable or equivalent); keep the new values as ordinary stored properties rather than adding @Published properties.

Full details: Cmux Swift Package Boundaries

Explanation

The diff adds MachineCreateOperation.hitMachineLimit in the app target. It identifies the service error by searching failure output for vm_active_limit_exceeded, and MachineCreateLoadingContent.offersUpgrade uses that result to decide whether to show Upgrade Plan. This error classification is independent of UI and belongs with the package-owned create lifecycle: CmuxCloudMachines.CloudMachineCreateOperation already models .failed(output:) and has a SwiftPM test target.

Resolution

Move the active-machine-limit error classification into the CmuxCloudMachines package, preferably as a computed property on CloudMachineCreateOperation (or a small package-level failure type). Make the app’s MachineCreateOperation delegate to that package API, and test the classification in CmuxCloudMachinesTests.

Full details: Cmux Swiftui State Layout

Explanation

The diff adds new @Published SwiftUI state and writes state from the layout pass. Sources/FileExplorerState.swift adds @Published modeBarMinimumWidth to FileExplorerState: ObservableObject; Sources/Cloud/MachinesPanelViewModel.swift adds @Published isRefreshingOnRequest. These are deliberate new state, not incidental edits to existing state. Also, RightSidebarModeBarTabsLayout.sizeThatFits calls widthReport.note, which schedules onChange with DispatchQueue.main.async; RightSidebarPanelView assigns that callback to update fileExplorerState.modeBarMinimumWidth. This is a deferred state write originating in layout, which the rule explicitly flags. The repository also contains existing @Observable models, confirming that the modern observation shape is in use.

Resolution

Move the new SwiftUI-owned state to @Observable models owned with @State, or pass immutable snapshots and action closures rather than adding new @Published properties to the legacy ObservableObject models. Remove the state update from RightSidebarModeBarTabsLayout.sizeThatFits; report the required width through a localized geometry or lifecycle callback, or derive it without mutating observable state during rendering. Ensure any callback updates state only in an explicit event or lifecycle path, not by deferring a write from the layout pass.

✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autopilot · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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

@github-actions

github-actions Bot commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor

CI failure attribution

CI failed on 7876bb9bc5 (run 37249069484 attempt 2): 1 code.

Job Verdict Why
macos / app-host unit tests (changed suites) code a test failed
Matched log lines
macos / app-host unit tests (changed suites): ✘ Test "Opening the Ports tab requests discovery once; closed Ports and collapsed machines do not scan" recorded an issue at CloudPortsVPNAffordanceTests.swift:285:9: Expectation failed: (requested → [opened, closed, collapsed]) == ([.cloud("opened")] → [opened])

Not re-run automatically: macos / app-host unit tests (changed suites) is not a machine failure.

Written by scripts/ci/classify_failures.py (ci-failure-attribution.yml); signatures are its SIGNATURES table. A machine verdict is the runner's fault, not this PR's.

@github-actions

github-actions Bot commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor

Dogfood tours of 7876bb9b

cloud-sidebar-audit-tour at 7876bb9b: failure (run)

Failed: DogfoodScenarioUITests.swift:115: failed - Dogfood steps failed:

cloud-sidebar-audit-tour at 7876bb9b

Key frames of cloud-sidebar-audit-tour at 7876bb9 02-failed 04-10-right-sidebar-files 06-failed 16-20-cloud-spacing-lab

Tours are picked by the paths globs in dogfood/scenarios/*.json; a Dogfood-tours: a, b line in the description picks them instead (none turns this off). Look at every frame before merging: a green tour only means no step failed.

review findings on the lift:
- closing open machines above the source moved its row up, and that
  shift counted as pointer travel, so a small nudge could move the
  machine several slots. only the pointer's travel picks the slot now;
  the held row glides from under the hand into its closed slot
- a drag whose end is never reported cancels once the button has been
  up for a moment, instead of following the pointer until the next click
- the release lays out before landing, so the rows glide instead of snapping
- mode bar: a late gesture change after the release no longer starts a
  phantom drag, and tab frames are measured without the drag offset
- tests: the lifted drop gets a proposal that disagrees with the lift, and
  a new test opens machines above the held one
lucasr1b and others added 9 commits October 3, 2026 14:48
…l tab names, smoother switching and resize, upgrade at the limit

- ports tab: the status text and its button start on the ports tab's edge
- an empty fleet keeps a "No cloud machines yet" line, so cloud machines
  keeps its chevron; my devices says "No other devices yet"
- the open-as-pane button stays in the bar in every tab; a tab that can't
  open as a pane says so in its tooltip
- the right sidebar's minimum (and opening) width follows the mode tabs
  shown, so every tab name fits with the trailing buttons
- switching tabs moves one highlight with no crossfade, and row
  selection is a layer so it no longer flickers while the sidebar resizes
- a failed create keeps its warning icon on the name's line
- a create refused at the plan's machine limit offers Upgrade Plan when a
  bigger plan exists
- the cloud machines refresh icon spins only for a refresh someone asked for
…cons; ports layout back as it was

the minimum and opening width now leave room for one full tab name (the
widest, so it doesn't change as the selection moves) with every other
tab at its icon, instead of every name in full. the ports tab's status
alignment goes back to main's.
…reate keeps its status readable

the cloud machines and my devices refresh icons move from the header's
trailing buttons to right after the title and count, small and in the
count's grey. the header passes clicks through to the row everywhere but
the icon, so clicking the header still opens and closes the section.

in a narrow sidebar a failed create's name gives way before its status.
…er other devices"

- the section headers' refresh icons take the same hover fill as + and
  ⋯; their resting color and size are unchanged
- switching tabs no longer moves a highlight across the bar. the widths
  ease on one short curve with no overshoot, the old tab's fill fades as
  it narrows and the new one's as it opens, labels are uncovered by their
  slot (with a soft edge) instead of re-truncating every frame, and an
  icon-only tab's icon glides to its leading spot
- my devices and settings say "Discover other devices" (and "Stop
  discovering other devices"), in all 9 locales
…olish

brings the ports status fix (only its button acts) and its hover chip
into this build. connectingLeading moves to the machine detail tabs
extension with the helpers #17070 moved there.
the row drops the "New Machine" name once the create has failed and
shows only the warning and "Couldn't create machine"; the name and the
reason stay in its tooltip and on the create's page.
@lucasr1b
lucasr1b marked this pull request as ready for review October 3, 2026 02:55
@cursor

cursor Bot commented Oct 3, 2026

Copy link
Copy Markdown

Bugbot is paused — on-demand spend limit reached

Bugbot uses usage-based billing for this team and has hit its on-demand spend limit.

A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue.

…toggling the section

each reload of a section header cleared the icon's clickable spot, and
SwiftUI reports the icon's frame only when it moves, so after the first
reload the click fell through to the row and opened or closed the
section. a header now keeps the spot across reloads; it clears only when
the cell is reused for another kind of row.
takes #17070's review fixes and its main merge. conflicts: ports status
keeps the chip title and hides an empty message; node actions keep both
newWorkspaceOnResolvedMachine and the section refreshes; the lift card
fill keeps the hover tint on the struct style; the panel keeps the header
refresh with main's reveal; the project file is #17070's with this
branch's files wired and the deleted refresh button removed.
both header icons use the panel's one refresh (fleet and devices) instead
of two new single-section actions; each icon still spins only for its own
section. shorter ports status comment and denser panel arguments.
@cursor

cursor Bot commented Oct 3, 2026

Copy link
Copy Markdown

Bugbot is paused — on-demand spend limit reached

Bugbot uses usage-based billing for this team and has hit its on-demand spend limit.

A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue.

@austinywang

Copy link
Copy Markdown
Contributor

The PR advanced to 47b3c5e15ef1e9c04f3a3193eba397aaf1c6dd4a with the latest Cloud follow-up fix. I built that exact head successfully.

The tagged app is launched in an isolated window. Current CI still reports failures in routed checks and changed-suite app-host tests; the PR remains mergeable.

— unregistered

@austinywang

Copy link
Copy Markdown
Contributor

Fixed the failed-create row UI at 437f18ff3cb03985950b42d3d4d08bf030ff1463.

Failed creates now render only the warning icon and Couldn't create machine error. The truncated request label (New…) is removed from the visual row, while accessibility and tooltip details retain the request context.

Focused checks pass: Swift syntax and test wiring. Exact-head fleet build completed:

The tagged app is launched in an isolated window.

— unregistered

@austinywang
austinywang merged commit 9aefea4 into main Oct 5, 2026
74 checks passed
@austinywang
austinywang deleted the cloud-sidebar-polish branch October 5, 2026 02:20
@github-actions

github-actions Bot commented Oct 5, 2026

Copy link
Copy Markdown
Contributor

Merge receipt for abae4be97b, merged 2026-10-05 02:20:16 UTC

  • Not verified at merge: ci-status (not reported), macOS compile admission (in progress)
  • Verified: backend migrations applied, agent-session-web-resources, catalog-structure, CI fast guards, detect-ios-changes, Fast static checks, GhosttyKit release check, guards (19), host-tests, ios-tests, linux-preflight, macOS admission gate, and 9 more
  • Skipped by policy: admission-placement, apply-production, apply-staging, Claude wrapper regressions, diff-sidecar-check, Dogfood build #​${{ github.event.pull_request.number }}, full-suite-coverage, ios-simulator, ios-simulator-build, mobile-core-package, react-apps-check, remote-daemon, and 11 more
  • Full suite: runs on main after merge.

Labeled merged-unverified: if main breaks near this merge, look here first.

@github-actions github-actions Bot added the merged-unverified A judging check was not green at merge; see the merge receipt comment label Oct 5, 2026
rustybret pushed a commit to rustybret/bmux that referenced this pull request Oct 5, 2026
7997c55 fix(cloud): update machine rename optimistically (manaflow-ai#17324)
c9bdbd6 Fix missing Terminals tab while Cloud machine connects (manaflow-ai#17326)
b047fa3 Fix Agent Hibernation never selecting live Claude Code sessions (manaflow-ai#17306)
9aefea4 cloud sidebar polish: header refresh, tab switch, empty states, errors and upgrade (manaflow-ai#17074)

# Conflicts:
#	.github/workflows/ci-guards.yml
#	.github/workflows/ci.yml
austinywang added a commit that referenced this pull request Oct 5, 2026
A connecting machine keeps its Terminals tab (#17326), and every visible
machine row requests a cached port scan once (#17074). Both tests still
asserted the earlier behavior and failed on main.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
austinywang added a commit that referenced this pull request Oct 5, 2026
* test(cloud): display health checks must not black-list websockify viewers

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(cloud): stop Xvnc black-listing every viewer of additional displays

Xvnc black-lists a peer after five unauthenticated connections, for a
timeout that doubles each time. Every viewer of an additional display is
websockify on 127.0.0.1, and the helper's 30s health check read the RFB
greeting and hung up, so displays soon answered every connection with
"Too many security failures". Restored panes and quiet retries then
failed for minutes.

Start additional displays with -UseBlacklist=0 (SecurityTypes None behind
the private network gives it nothing to protect; Xvnc refuses runtime
changes), complete the None handshake in the health check so it clears
marks instead of adding them, and replace recovery's pgrep patterns, which
pgrep's regex dialect rejected, with exact argv matching.

Each helper request also runs in a plain shell now: the login shell cost
about 0.7s per call and only the long-lived service needs its environment.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(cloud): New Display hands over a running standby display

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* perf(cloud): keep one standby display running per desktop VM

Each New Display started Xvnc, a desktop session and websockify on the
guest (about 0.7s) while the user waited. The guest helper now keeps one
display running outside the catalog, hands it over on create (about 0.1s)
and starts the next in the background. The standby is invisible to list,
does not count against capacity, and a restarted helper adopts it instead
of leaking it. Opening a machine's Displays tab, or restoring it selected,
runs guest discovery once so the standby is warm before the first click.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(cloud): failed or launching standby displays are never misassigned

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(cloud): serialize standby handover; retire failed standbys; rediscover after wake

Concurrent creates could record a launching standby's number as a normal
display; take the standby under one lock and reserve running numbers. A
standby whose start failed is stopped and replaced instead of handed over
or retried forever. Displays-tab discovery runs once per open and retries
when a sleeping machine wakes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(cloud): align two sidebar tests with merged behavior

A connecting machine keeps its Terminals tab (#17326), and every visible
machine row requests a cached port scan once (#17074). Both tests still
asserted the earlier behavior and failed on main.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(cloud): standby never takes a recorded number; failed discovery retries

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(cloud): address review: standby race and Displays discovery retry

ensure_standby's locked check now also rejects a number a concurrent
create recorded after the unlocked probe, so a display can never be both
a catalog display and the standby.

Displays-tab discovery reports completion; a failed discovery clears the
request and retries, bounded to three attempts per shown machine, instead
of treating task launch as done.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(cloud): connecting machines keep only the Resources tab (#17139)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(cloud): a stale Displays discovery cannot retry after the tab reopens

Drive discovery completions explicitly instead of yielding a fixed number of times.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(cloud): bind each Displays discovery completion to its request

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
austinywang added a commit that referenced this pull request Oct 5, 2026
…#17327)

* test(cloud): display health checks must not black-list websockify viewers

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(cloud): stop Xvnc black-listing every viewer of additional displays

Xvnc black-lists a peer after five unauthenticated connections, for a
timeout that doubles each time. Every viewer of an additional display is
websockify on 127.0.0.1, and the helper's 30s health check read the RFB
greeting and hung up, so displays soon answered every connection with
"Too many security failures". Restored panes and quiet retries then
failed for minutes.

Start additional displays with -UseBlacklist=0 (SecurityTypes None behind
the private network gives it nothing to protect; Xvnc refuses runtime
changes), complete the None handshake in the health check so it clears
marks instead of adding them, and replace recovery's pgrep patterns, which
pgrep's regex dialect rejected, with exact argv matching.

Each helper request also runs in a plain shell now: the login shell cost
about 0.7s per call and only the long-lived service needs its environment.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(cloud): New Display hands over a running standby display

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* perf(cloud): keep one standby display running per desktop VM

Each New Display started Xvnc, a desktop session and websockify on the
guest (about 0.7s) while the user waited. The guest helper now keeps one
display running outside the catalog, hands it over on create (about 0.1s)
and starts the next in the background. The standby is invisible to list,
does not count against capacity, and a restarted helper adopts it instead
of leaking it. Opening a machine's Displays tab, or restoring it selected,
runs guest discovery once so the standby is warm before the first click.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(cloud): failed or launching standby displays are never misassigned

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(cloud): serialize standby handover; retire failed standbys; rediscover after wake

Concurrent creates could record a launching standby's number as a normal
display; take the standby under one lock and reserve running numbers. A
standby whose start failed is stopped and replaced instead of handed over
or retried forever. Displays-tab discovery runs once per open and retries
when a sleeping machine wakes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(cloud): a closed display view is not rebuilt from a stale graph

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(cloud): load every workspace display and keep closed displays closed

Displays beyond the first in a Cloud workspace layout appeared late: the
catalog only learns display:N from guest discovery and drops memberships
for displays it does not know, and nothing on open or restore ran
discovery. Publishing a graph whose memberships name unknown displays now
runs discovery once per lifecycle generation.

Closed display panes came back: the membership removal is queued while
reconciliation still reads the graph holding the token, so it rebuilt the
pane under a new panel id and left an orphaned token that resurrected it
after every close. Closing now fences the view until a fetched graph drops
its token, retries a removal that has not landed, and a pane rebuilt from
an orphaned token removes that token.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(cloud): discover member displays on deltas; bound and scope token cleanup

Membership rows arrive as projection deltas, so discovery also runs from
publishDelta. Orphan cleanup skips other Macs' tokens (a no-op write that
still cost a guest snapshot), removal retries stop after three attempts,
and the client id is read once per close.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(cloud): prune this Mac's orphaned display memberships in open workspaces

A membership token whose pane is gone (an old close whose removal never
landed, a crash, a pane rebuilt under a new id) showed as a duplicate
display row and resurrected the display after it was closed; the
reconciler treats one live pane as satisfying every token for that
display, so nothing removed it. After reconciling an open Cloud
workspace, remove this Mac's tokens with no live display pane, once the
machine's restore has settled. Tokens of workspaces not open here, and of
other Macs, are kept as their layout.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(cloud): keep display discovery results across machine-list polls

refreshDisplays dropped its result whenever refreshGeneration changed, and
every machine-list poll bumps it, so a ~2s guest discovery that overlapped
a poll was discarded: workspace displays loaded only after a later lucky
discovery. Check the lifecycle generation only; the coordinator already
invalidates on identity or image changes. Member-display discovery also
re-checks after each attempt (bounded to three per lifecycle) because a
launch-time refresh can cancel one, verified live: the first attempt's
exec was cancelled after 1ms.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(cloud): member display listed once; silent desktop probe is not unreachable

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(cloud): list a workspace display once; stop repairing healthy desktops

The sidebar listed a workspace's display twice: cloudWorkspaceResources
appends a copy of a member display carrying its membership view, and
localWorkspaceMembers deduped against the first copy (the pool resource,
without the view), so the local pane added the display again.

Display 1 sat on "Loading Cloud page" for 20s+: the desktop probe's 2s
deadline never cancelled its connection, so a busy carrier held it to the
proxy's 10s header timeout, and the timeout read as unreachable and ran
the control plane's desktop repair (a guest exec, ~12s) on a healthy
desktop. The deadline now cancels the connection, and repair runs only
when the proxy or service answered with an error.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(cloud): a New Display pane and its membership are one sidebar row

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(cloud): build workspace rows and local-pane rows from one resource list

The sidebar built workspace rows from cloudWorkspaceResources (which holds
the membership copy of a member display) but deduped local panes against
the pool resources alone, so a New Display pane listed its display twice.
Both passes now read the same list.

Adds debug.cloudtree.rows (DEBUG only), returning the Cloud sidebar's rows
from the same builder, so dogfood can assert listed rows; duplicate rows
were invisible to cloud tree --json.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(cloud): display names parse from the daemon graph

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* feat(cloud): named displays, display tab titles, remove a display from a workspace

Display panes showed noVNC's page title, and renaming a workspace display
sent a synthetic view id to the daemon's tab rename, which cannot work.
Displays now have one shared name per display, stored as a frontend
projection beside the workspace memberships so every client follows a
rename live. Rows and pane tabs show it ("Display N" until renamed);
renaming the tab or the row renames the display; clearing restores the
number.

Workspace display rows get a hover X and a "Remove from Workspace" menu
item, which close the display's pane on this Mac (the fenced membership
removal) or remove this Mac's membership when no pane is open.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(ci): list debug.cloudtree.rows as a debug method; fold display names into the membership type

The socket capability guard requires debug methods to be listed as
intentionally unadvertised, and the package conventions lint rejects an
all-static enum, so the display-name constants move onto
CloudVMDisplayMembership.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(cloud): a display row's Rename names the display

The menu now routes display renames to renameDisplay (one name per
display, shared by every row and pane) instead of a view's tab rename.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(cloud): align two sidebar tests with merged behavior

A connecting machine keeps its Terminals tab (#17326), and every visible
machine row requests a cached port scan once (#17074). Both tests still
asserted the earlier behavior and failed on main.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(cloud): the desktop placeholder is titled Display 1

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(cloud): title display panes from the click, never Desktop first

The display 1 placeholder (shown before guest discovery) was titled
"Desktop" and renamed "Display 1" seconds later; it now uses the same
numbered title as discovery. A new display's pane is titled "Starting
display…" from the click instead of "New tab", then takes the
display's name when it materializes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(cloud): standby never takes a recorded number; failed discovery retries

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(cloud): address review: standby race and Displays discovery retry

ensure_standby's locked check now also rejects a number a concurrent
create recorded after the unlocked probe, so a display can never be both
a catalog display and the standby.

Displays-tab discovery reports completion; a failed discovery clears the
request and retries, bounded to three attempts per shown machine, instead
of treating task launch as done.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(cloud): connecting machines keep only the Resources tab (#17139)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(cloud): a stale Displays discovery cannot retry after the tab reopens

Drive discovery completions explicitly instead of yielding a fixed number of times.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(cloud): bind each Displays discovery completion to its request

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(cloud): closing a display pane removes it for every client

Drives the real pane-close path. Another client's token in the same
workspace must not rebuild the pane, including when the close lands before
this pane's own token reaches the graph.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(cloud): closed displays stay closed across clients and instances

- Closing a display pane or clicking X removes the display from that Cloud
  workspace for every client. The fence is keyed by machine, workspace and
  display, set at close from the pane's own workspace, and lifted when this
  Mac opens the display there again.
- Pruning only touches view IDs this process recorded: stable and DEV
  builds on one Mac share a client ID.
- A sleeping machine or an undiscovered display does not spend a removal
  attempt.
- Tab renames run in order, and afterwards every pane shows the display's
  real name, so a cleared or failed rename reverts the tab.
- A failed reserved pane drops its "Starting display…" title.
- A reset or refused connection after the tunnel opens reads as unreachable.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(cloud): a desktop that resets the opened tunnel is unreachable

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(cloud): read a reset after the desktop tunnel opens as unreachable

The probe's errors arrive as NWConnection.StreamError, so the previous NWError
cast never matched. Only the HEAD stage maps a reset or refusal; before the
tunnel opens the same error belongs to the local proxy and stays unknown.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(cloud): a queued close cannot undo a reopen; another client's re-add shows again

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(cloud): order display removals against reopens; release on another client's re-add

- A reopen bumps the display's generation, so a close or removal retry that
  started before it leaves the reopened display alone.
- Removal retries run on the machine's lane, ordered with the reopen's
  attach; the lane's enqueue is split into a resource-keyed core.
- A removal records the tokens it deleted. A later graph showing any other
  token means another client put the display back, so the fence lifts.
- Attempts are counted per closed display and only while it is fenced.
- A tab rename re-applies the display's name only when it changed nothing or
  failed, so a successful rename no longer flashes the old name.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(cloud): type the lane failure handler explicitly

The ternary of closures crashed the type checker (failed to produce diagnostic).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* test(cloud): a no-op display removal still fences older graphs; proxy reset is unknown

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(cloud): release a closed display by graph revision, not token sets

A removal now reports the cursor of the graph it was computed from. A graph
no newer than that predates the removal and stays fenced, including when the
removal found nothing to delete. A newer graph that still shows the display
means another client put it back.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* fix(cloud): fence a closed display up to its removal write's own cursor

A graph published between the removal's snapshot read and its write could be
newer than the snapshot yet still hold the removed tokens, releasing the fence
early. The removal now reports the write receipt's cursor (the snapshot's only
for a no-op), and a reply without one keeps the fence until the display is gone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
teamleaderleo added a commit that referenced this pull request Oct 5, 2026
* ci: name the failing test and whose it is in the CI attribution comment

The attribution comment said "code" per job; #17074, #17232 and #17233 merged
red on 10-05 with no comment naming their own failing tests, and #17232 kept a
stale "CI passes" from an older head.

- classify_failures.py extracts concrete failures from each failed job's
  failed steps (Swift Testing and XCTest file:line, compile errors, crashes,
  warning-budget overages, CLI help contract), dropping system and cache
  error: noise, and marks each yours (a file the PR changes or tests), also
  red on main (main's latest red full suite), or new in this PR. The
  comment's first line is that verdict.
- The comment records its head; a newer head's CI start (workflow_run
  requested) or an older head's completion rewrites it to pending. A PR merged
  before CI finished still gets the comment. A green run whose macOS jobs never
  ran says so instead of "passes".
- main_full_suite.py lists the red run's concrete failures in the issue with a
  data marker the PR attribution reads.
- main_regression_attribution.py @-mentions each suspect's merger once.
- guard_attribution.py's green comment says it covers only the fast guards.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* ci: harden failure attribution per review

- Trust only the bot's issue body and comments for main's failure data, so a
  commented marker cannot turn a PR's own failure into "Not yours".
- An unreadable files list or main issue falls back to none and still writes
  the comment.
- `requested` events get their own concurrency group, so a start never
  replaces a pending completed report and its machine re-run.
- "macOS jobs did not run" only when ci.yml's macos prerequisites (changes,
  Fast static checks) did not succeed, not when routing reused an admitted
  compile; a test pins the names to ci.yml.
- Compiler paths match the PR's file by path; a bare Swift Testing file name
  says when several changed files share it.
- No "fix forward" on a merged PR whose failures are all machine.
- URL-encode the head branch in PR lookups; job names go through code().

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

* ci: keep failure reports quiet when nothing new happened

A main that stays red with the same failures edits its last report on the
tracking issue instead of posting a new comment, so subscribers and pinged
mergers are notified only when the failure set changes. A PR whose failures
are all the machine's and are being re-run gets no new comment.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
lawrencecchen added a commit that referenced this pull request Oct 7, 2026
A project file merge in #17074 dropped
the browser-repl resource folder from the app target, so every
`cmux browser repl` call fails with "browser REPL runtime manifest is
missing". Restore the folder reference and its Resources build file.
austinywang added a commit that referenced this pull request Oct 10, 2026
A project file merge in #17074 dropped
the browser-repl resource folder from the app target, so every
`cmux browser repl` call fails with "browser REPL runtime manifest is
missing". Restore the folder reference and its Resources build file.

Co-authored-by: Austin Wang <38676809+austinywang@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

merged-unverified A judging check was not green at merge; see the merge receipt comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants