iOS: add pane-grouped deck and Safari-style pane map - #8188
azooz2003-bit wants to merge 89 commits into
Conversation
Adds a layout object per workspace to mobile.workspace.list: the recursive bonsplit split tree (orientation + divider ratio) and per-pane surface tabs (panel UUID, panelType, title, selected tab), gated by the new workspace.layout.v1 capability. The list observer's summary hash now covers pane topology (pane ids, per-pane tab order, selected tab) while still excluding split ratios and focus-only changes. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
MobilePaneLayout mirrors the layout.v1 wire shape: recursive split/pane nodes with per-pane surfaces (type-aware, forward-compatible other case), plus pure helpers for DFS pane order, surface lookup, and unit-space pane rectangles. Decoding is tolerant: a malformed or missing layout leaves the workspace usable with layout nil. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The top-right terminal dropdown is gone. A persistent bottom deck now shows every surface in the workspace, chips grouped by Mac pane (from layout.v1), with type icons, agent-status dots (working/needs-input via chat sessions), one-tap stream switching, a + menu (terminal/browser/workspace), and a pane-map button. Remaining utilities (View as Text, Copy Debug Logs, Send Feedback) move to a slim ellipsis menu. Deck visibility branches inside the safe-area inset so the terminal/chat/browser subtree keeps stable structural identity; keyboard covers the deck instead of lifting it. All new strings localized en+ja; XCUITest coverage ported from the dropdown to the deck. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Full-screen map of the Mac workspace's real split geometry: proportional tiles from the layout.v1 tree on an aspect-fit 16:10 canvas, live render-grid text previews per terminal (one-shot mobile.terminal.replay side channel that omits viewport params so previewing can never resize the shared grid, capped at 4 concurrent fetches, selected tabs first), mini tab pills on multi-tab panes, agent-status dots, phone-selected accent border, and tap-to-jump. Non-terminal surfaces render disabled at 50% opacity. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
CMUX_UITEST_PANES_PREVIEW=1 mounts the production SurfaceDeckBar and PaneMapOverlay against a realistic 3-pane fixture workspace (multi-tab panes, agent statuses, canned render-grid previews) so the panes/tabs UI can be exercised and screenshotted without sign-in or a paired Mac. Also tightens accessibility labels/identifiers on the pane map controls. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Deck: themed opaque backdrop extending into the home-indicator region (theme foreground was invisible on the system background), no shared GlassEffectContainer (its glass layer composited scrolled-away chips above the scroll clip, bleeding under the fixed controls), explicit theme foreground on the pane-map and add buttons (accent-blue tint mismatch). Pane map: labeled mini tab pills instead of anonymous dot capsules, canvas fills the available rect instead of forcing 16:10 (split ratios carry the structure; bigger previews read better on a portrait phone), preview font cap raised to 9pt. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThe change adds versioned workspace pane-layout transport, recursive iOS layout models, terminal preview rendering, bounded preview fetching, and Surface Deck and pane-map UI flows. Workspace detail navigation and related unit, fidelity, UI, localization, and CI coverage are updated. ChangesWorkspace layout transport and models
Surface Deck and pane-map experience
Estimated code review effort: 5 (Critical) | ~120 minutes Sequence Diagram(s)sequenceDiagram
participant MacWorkspace
participant MobileWorkspaceList
participant MobileWorkspacePreview
participant SurfaceDeckBar
participant PaneMapOverlay
MacWorkspace->>MobileWorkspaceList: serialize layout.v1 workspace payload
MobileWorkspaceList->>MobileWorkspacePreview: decode and map layout
MobileWorkspacePreview->>SurfaceDeckBar: provide ordered pane and surface groups
SurfaceDeckBar->>PaneMapOverlay: present pane map
PaneMapOverlay->>MacWorkspace: fetch terminal replay grids
MacWorkspace->>PaneMapOverlay: return render-grid previews
Possibly related PRs
Suggested reviewers: Important Pre-merge checks failedPlease resolve all errors before merging. Addressing warnings is optional. ❌ Failed checks (4 errors, 1 warning, 1 inconclusive)
✅ Passed checks (19 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
Greptile SummaryThis PR introduces the iOS pane map: a Safari-style collection view of all workspace panes with live previews, native drag-to-reorder that mutates the Mac's authoritative Bonsplit layout, and a workspace-local
Confidence Score: 4/5Safe to merge after the failure-path focus restore in The Mac-side Files Needing Attention: Sources/Workspace+MobilePaneReorder.swift — the failure-path branch inside Important Files Changed
Sequence DiagramsequenceDiagram
participant iOS as iOS (PaneMapCollectionView)
participant State as PaneMapReorderState
participant Shell as MobileShellComposite
participant Mac as TerminalController (Mac)
participant WS as Workspace (Mac)
iOS->>State: beginMove(from:, to:) → Request
State-->>iOS: optimistic visiblePaneIDs updated
iOS->>Shell: reorderWorkspacePanes(id:, orderedPaneIDs:)
Shell->>Mac: workspace.pane.reorder (RPC)
Mac->>WS: applyMobilePaneOrder(orderedPaneIDs)
Note over WS: suppressionCount += 1<br/>create placeholders<br/>moveTab per pane<br/>clean placeholders<br/>suppressionCount -= 1
alt success
WS-->>Mac: true
Mac->>Mac: v2MobileWorkspaceList (authoritative layout)
Mac-->>Shell: updated workspace list with layout
Shell->>State: reconcile(authoritativePaneIDs)
Shell->>State: complete(requestID, succeeded: true)
State-->>iOS: "visiblePaneIDs = authoritativePaneIDs"
else failure
WS-->>Mac: false (rollback applied)
Mac-->>Shell: error response
Shell->>State: complete(requestID, succeeded: false)
State-->>iOS: "rolledBack, visiblePaneIDs = authoritativePaneIDs"
iOS->>iOS: haptic error feedback
end
Reviews (18): Last reviewed commit: "fix(ios): prefer focused pane terminal o..." | Re-trigger Greptile |
There was a problem hiding this comment.
Actionable comments posted: 8
🤖 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/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite`+PaneMapPreviews.swift:
- Line 31: Update the preview-loading flow around
MobileTerminalReplayResponse.decode to perform the synchronous JSON decoding
inside a Task.detached context, then await its result before accessing
renderGrid; keep the existing decoded response and error behavior unchanged.
In
`@Packages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobilePaneSplit.swift`:
- Around line 7-30: Enforce the 0.05...0.95 invariant in the public
MobilePaneSplit initializer by clamping or rejecting the incoming ratio before
assigning self.ratio. Ensure every constructed split is valid so
normalizedRects() can safely use ratio without producing invalid frames, while
preserving valid ratio values unchanged.
In
`@Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/PaneMapOverlay.swift`:
- Around line 138-161: Replace the String(format:) calls in the pane-count
formatting cases with String.localizedStringWithFormat, preserving the existing
localized keys, default values, and paneCount/tabCount arguments so number
formatting follows the user’s locale.
In
`@Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/PaneMapPreviewRenderer.swift`:
- Around line 38-44: Update the row-text placement loop in
PaneMapPreviewRenderer.rows to advance and clip columns using each RowSpan’s
cellWidth rather than treating every grapheme in span.text as one cell. Reuse
the existing width-aware span or clipping logic if available, while preserving
row and bounds validation so wide glyphs occupy their full terminal width.
In
`@Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/PaneMapTileView.swift`:
- Around line 17-20: Update the selectedSurface computed property in
PaneMapTileView so a non-nil selectedSurfaceID that does not match any pane
surface returns nil instead of falling back to pane.surfaces.first; preserve the
existing first-surface default only when no selection ID is provided, preventing
mismatched rendering with PaneMapOverlay lookups.
In
`@Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/PanesTabsPreviewHost.swift`:
- Around line 1-7: Isolate the panes fixture behind the dedicated debug-preview
boundary: move PanesTabsPreviewHost into the Debug area, centralize its preview
gate and construction in the debug-preview extension/router, and replace the
fixture-specific branch in CMUXMobileRootView with the shared debug-preview
entry point. In
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/PanesTabsPreviewHost.swift#L1-L7
move the fixture; in
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/CMUXMobileRootView.swift#L73-L80
and `#L113-L120` relocate the gate and construction, and at `#L229-L230` use the
shared entry point; in
Packages/iOS/CmuxMobileSupport/Sources/CmuxMobileSupport/UITestConfig.swift#L82-L93
keep the environment flag private to debug-preview configuration.
In
`@Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/SurfaceDeckBar.swift`:
- Around line 117-121: The agent status indicator is currently conveyed only
through an unlabeled colored dot. In SurfaceDeckBar.swift at lines 117-121 and
PaneMapTileView.swift at lines 73-77, add the same localized “working” or “needs
input” state to each view’s accessibility label or value, reusing the existing
statusKind localization and preserving the current visual indicators.
In
`@Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView.swift`:
- Around line 145-165: Update the pane-map presentation state around
fullScreenCover and workspace.layout so isPaneMapPresented is cleared whenever
the required layout becomes nil, including after refresh or reconnect. Preserve
the existing PaneMapOverlay construction when a layout is available, and ensure
the full-screen cover cannot remain presented without a valid layout.
🪄 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: 851bbac2-0921-48ed-a8ee-a53962f4a222
⛔ Files ignored due to path filters (1)
ios/cmux.xcworkspace/xcshareddata/swiftpm/Package.resolvedis excluded by!**/Package.resolved
📒 Files selected for processing (41)
Packages/iOS/CmuxMobileRPC/Sources/CmuxMobileRPC/MobileSyncWorkspaceListResponse.swiftPackages/iOS/CmuxMobileRPC/Sources/CmuxMobileRPC/MobileWorkspacePreview+RemoteMapping.swiftPackages/iOS/CmuxMobileRPC/Tests/CmuxMobileRPCTests/MobilePaneLayoutDecodeTests.swiftPackages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+PaneMapPreviews.swiftPackages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobilePaneLayout.swiftPackages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobilePaneNode.swiftPackages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobilePaneSplit.swiftPackages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobilePaneSplitOrientation.swiftPackages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobilePaneSurface.swiftPackages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobilePaneSurfaceType.swiftPackages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobileWorkspacePreview.swiftPackages/iOS/CmuxMobileShellModel/Tests/CmuxMobileShellModelTests/MobilePaneLayoutTests.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/CMUXMobileRootView.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/ChatAgentStateKind.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/PaneMapOverlay.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/PaneMapPreviewRenderer.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/PaneMapTileView.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/PaneMapValue.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/PanesTabsPreviewHost.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/SurfaceDeckActions.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/SurfaceDeckBar.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/SurfaceDeckValue.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TerminalPickerMenu.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TerminalPickerMenuActions.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TerminalPickerMenuDiagnostics.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TerminalPickerMenuValue.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailCreateDelayedTerminalPreviewView.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView+AgentChat.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView+MenuState.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceUtilitiesMenu.swiftPackages/iOS/CmuxMobileShellUI/Tests/CmuxMobileShellUITests/PaneMapPreviewRendererTests.swiftPackages/iOS/CmuxMobileShellUI/Tests/CmuxMobileShellUITests/SurfaceDeckValueTests.swiftPackages/iOS/CmuxMobileShellUI/Tests/CmuxMobileShellUITests/TerminalPickerMenuValueTests.swiftPackages/iOS/CmuxMobileSupport/Sources/CmuxMobileSupport/UITestConfig.swiftSources/Mobile/MobileHostService+Capabilities.swiftSources/Mobile/MobileWorkspaceListObserver.swiftSources/TerminalController+MobileWorkspaceList.swiftcmuxTests/MobileWorkspaceListFidelityTests.swiftios/cmux/Resources/Localizable.xcstringsios/cmuxUITests/cmuxUITests.swift
💤 Files with no reviewable changes (6)
- Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TerminalPickerMenuDiagnostics.swift
- Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TerminalPickerMenuValue.swift
- Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TerminalPickerMenu.swift
- Packages/iOS/CmuxMobileShellUI/Tests/CmuxMobileShellUITests/TerminalPickerMenuValueTests.swift
- Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView+MenuState.swift
- Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TerminalPickerMenuActions.swift
# Conflicts: # Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TerminalPickerMenu.swift # Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView.swift # cmuxTests/MobileWorkspaceListFidelityTests.swift
The NSImage drawing handler is escaping, so referencing the enclosing view's color without an explicit capture fails to compile; capture the color by value. Unbreaks clean Debug builds of current main carried into this branch. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Bugbot is paused — on-demand spend limit reachedBugbot 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. |
There was a problem hiding this comment.
♻️ Duplicate comments (1)
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView.swift (1)
145-165: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winDismiss the pane map when its required layout disappears.
isPaneMapPresentedcan remain true whileworkspace.layoutbecomesnilafter a refresh or reconnect, leaving an empty full-screen cover with no dismissal control. Clear presentation when the layout becomes unavailable so this invalid state is not representable.As per coding guidelines, Swift architecture changes must preserve clear ownership and invariants rather than patching symptoms while leaving bad state representable.
💡 Proposed fix
.fullScreenCover(isPresented: $isPaneMapPresented) { if let layout = workspace.layout { PaneMapOverlay( value: PaneMapValue( workspaceName: workspace.name, layout: layout, phoneSelectedSurfaceID: selectedTerminal?.id.rawValue, agentStateKindsBySurfaceID: surfaceDeckAgentStateKinds ), fetchPreviews: { selectedSurfaceIDs, remainingSurfaceIDs in await store.fetchPaneMapPreviewGrids( remoteWorkspaceID: workspace.rpcWorkspaceID.rawValue, selectedSurfaceIDs: selectedSurfaceIDs, remainingSurfaceIDs: remainingSurfaceIDs ) }, selectTerminal: selectTerminalFromDeck, dismiss: { isPaneMapPresented = false } ) } } + .onChange(of: workspace.layout == nil) { _, layoutIsMissing in + if layoutIsMissing { + isPaneMapPresented = false + } + }🤖 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/WorkspaceDetailView.swift` around lines 145 - 165, Update the pane-map presentation flow around isPaneMapPresented and workspace.layout so presentation is cleared whenever the required layout becomes nil, including after refresh or reconnect. Preserve the existing PaneMapOverlay construction for available layouts and ensure the fullScreenCover cannot remain presented without a valid layout.Source: Coding guidelines
🤖 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.
Duplicate comments:
In
`@Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView.swift`:
- Around line 145-165: Update the pane-map presentation flow around
isPaneMapPresented and workspace.layout so presentation is cleared whenever the
required layout becomes nil, including after refresh or reconnect. Preserve the
existing PaneMapOverlay construction for available layouts and ensure the
fullScreenCover cannot remain presented without a valid layout.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: a930f532-707b-4115-b0b3-c3adfe779d65
📒 Files selected for processing (3)
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/CMUXMobileRootView.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceUtilitiesMenu.swift
The preview renderer showed the last 20 grid rows, which is blank for a tall phone-attached grid whose content sits at the top after a clear; the window now ends at the last content-bearing row (or cursor). Deck, pane map, and preview host take the live TerminalTheme (TerminalPalette was removed on main by PR 7919); preview rows use the theme's true terminal foreground while chrome uses the readable chrome color. Adds fetch diagnostics to the pane-map replay side channel and regression tests for top-anchored and bottom-anchored preview windows. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (6)
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/PanesTabsPreviewHost.swift (1)
7-15: 📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy liftIsolate the panes fixture behind one dedicated Debug boundary.
The preview currently adds test-only types, configuration, and routing across production source/API surfaces. As per path instructions, production Swift under
Sources/must not add test/debug seams, and unavoidable debug facilities belong in a dedicated debug file or folder. Move the fixture into the package’s dedicatedDebug/area.🤖 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/PanesTabsPreviewHost.swift` around lines 7 - 15, Move PanesTabsPreviewHost and its preview-only fixture constants and routing into the package’s dedicated Debug area, keeping production Sources/ APIs free of test/debug seams. Preserve the existing preview behavior and surface identifiers after relocating the complete fixture behind that single debug boundary.Source: Path instructions
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView.swift (1)
146-167: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick winDismiss the pane map when its required layout disappears.
isPaneMapPresentedcan remain true whileworkspace.layoutbecomesnilafter a refresh or reconnect, leaving an empty full-screen cover with no dismissal control. Clear presentation when the layout becomes unavailable so this invalid state is not representable. As per coding guidelines, Swift architecture changes must preserve clear ownership and invariants rather than leaving bad state representable.🛡️ Proposed fix to clear presentation
.fullScreenCover(isPresented: $isPaneMapPresented) { if let layout = workspace.layout { PaneMapOverlay( value: PaneMapValue( workspaceName: workspace.name, layout: layout, phoneSelectedSurfaceID: selectedTerminal?.id.rawValue, agentStateKindsBySurfaceID: surfaceDeckAgentStateKinds ), terminalTheme: store.activeTerminalTheme, fetchPreviews: { selectedSurfaceIDs, remainingSurfaceIDs in await store.fetchPaneMapPreviewGrids( remoteWorkspaceID: workspace.rpcWorkspaceID.rawValue, selectedSurfaceIDs: selectedSurfaceIDs, remainingSurfaceIDs: remainingSurfaceIDs ) }, selectTerminal: selectTerminalFromDeck, dismiss: { isPaneMapPresented = false } ) } } + .onChange(of: workspace.layout == nil) { _, layoutIsMissing in + if layoutIsMissing { + isPaneMapPresented = false + } + }🤖 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/WorkspaceDetailView.swift` around lines 146 - 167, Update the pane-map presentation state around the fullScreenCover and workspace.layout dependency so isPaneMapPresented is cleared whenever workspace.layout becomes nil, including after refresh or reconnect. Preserve the existing PaneMapOverlay construction when a layout is available, and ensure the dismissal is owned by the surrounding WorkspaceDetailView state rather than leaving an empty cover presented.Source: Coding guidelines
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/PaneMapPreviewRenderer.swift (1)
48-56: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winUse the span cell width when placing row text.
PaneMapPreviewRenderer.rowswalksspan.textone grapheme at a time, butMobileTerminalRenderGridFrame.RowSpanalready carriescellWidth. Wide glyphs will be misaligned or truncated in pane-map previews unless this uses the width-aware span length or shared clipping logic.🤖 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/PaneMapPreviewRenderer.swift` around lines 48 - 56, Update the row text placement loop in PaneMapPreviewRenderer.rows to account for each RowSpan’s cellWidth rather than advancing one column per grapheme in span.text. Reuse the existing width-aware span-length or clipping logic so wide glyphs occupy the correct number of cells while preserving column bounds and truncation behavior.Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/SurfaceDeckBar.swift (1)
118-123: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winMake agent activity accessible across both surface views.
The “working” and “needs input” states are currently conveyed only by unlabeled colored dots. Add the localized agent state to the chip’s and tile's accessibility semantics.
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/SurfaceDeckBar.swift#L118-L123: add the localized agent state to the chip’s accessibility label or value.Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/PaneMapTileView.swift#L74-L78: add the same localized state to the tile’s accessibility semantics.🤖 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/SurfaceDeckBar.swift` around lines 118 - 123, Expose the localized agent state in the accessibility semantics for both the SurfaceDeckBar chip at Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/SurfaceDeckBar.swift#L118-L123 and the PaneMapTileView tile at Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/PaneMapTileView.swift#L74-L78. Reuse the existing localized state text and append it to each view’s accessibility label or value while preserving their current semantics.Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/PaneMapOverlay.swift (1)
142-165: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winUse
String.localizedStringWithFormatfor locale-aware number formatting.Using
String(format:)bypasses locale-aware number formatting for the%dplaceholders (such as localized grouping separators or digits). Based on learnings, when providing arguments to a plain localized string, preferString.localizedStringWithFormatto ensure proper locale application.🌍 Proposed fix for localized string formatting
case (true, false): - return String( - format: L10n.string( + return String.localizedStringWithFormat( + L10n.string( "mobile.paneMap.count.onePane.otherTabs", defaultValue: "1 pane · %d tabs" ), tabCount ) case (false, true): - return String( - format: L10n.string( + return String.localizedStringWithFormat( + L10n.string( "mobile.paneMap.count.otherPanes.oneTab", defaultValue: "%d panes · 1 tab" ), paneCount ) case (false, false): - return String( - format: L10n.string( + return String.localizedStringWithFormat( + L10n.string( "mobile.paneMap.count.otherPanes.otherTabs", defaultValue: "%d panes · %d tabs" ), paneCount, tabCount )🤖 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/PaneMapOverlay.swift` around lines 142 - 165, Replace the String(format:) calls in the pane-count cases of the PaneMapOverlay formatting logic with String.localizedStringWithFormat, preserving the existing localized keys, fallback strings, and paneCount/tabCount arguments.Source: Learnings
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/PaneMapTileView.swift (1)
18-21: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick winDon't fall back to
pane.surfaces.firstfor an invalid selected surface.A stale
selectedSurfaceIDcan make this tile render the first surface whilePaneMapOverlaystill uses the original ID for preview and agent lookups, so the UI can show mismatched data. As per path instructions, reconcileselectedSurfaceIDsByPaneIDwhen the layout changes, or returnnilhere so invalid selections fail closed instead of showing an unreliable fallback.🛡️ Proposed fix to fail closed
private var selectedSurface: MobilePaneSurface? { guard let selectedSurfaceID else { return pane.surfaces.first } - return pane.surfaces.first { $0.id == selectedSurfaceID } ?? pane.surfaces.first + return pane.surfaces.first { $0.id == selectedSurfaceID } }🤖 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/PaneMapTileView.swift` around lines 18 - 21, Update selectedSurface in PaneMapTileView so an invalid selectedSurfaceID returns nil instead of falling back to pane.surfaces.first. Preserve the existing first-surface behavior only when no selection ID is set; alternatively, reconcile selectedSurfaceIDsByPaneID during layout changes so stale IDs are removed before rendering and overlay lookups.Source: Path instructions
🤖 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/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite`+PaneMapPreviews.swift:
- Around line 32-33: Move the synchronous MobileTerminalReplayResponse.decode
call in the pane-map preview flow into a Task.detached operation, await its
result, and then access renderGrid after decoding completes. Preserve the
existing decoding behavior while ensuring JSON parsing does not execute on the
caller’s actor.
---
Duplicate comments:
In
`@Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/PaneMapOverlay.swift`:
- Around line 142-165: Replace the String(format:) calls in the pane-count cases
of the PaneMapOverlay formatting logic with String.localizedStringWithFormat,
preserving the existing localized keys, fallback strings, and paneCount/tabCount
arguments.
In
`@Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/PaneMapPreviewRenderer.swift`:
- Around line 48-56: Update the row text placement loop in
PaneMapPreviewRenderer.rows to account for each RowSpan’s cellWidth rather than
advancing one column per grapheme in span.text. Reuse the existing width-aware
span-length or clipping logic so wide glyphs occupy the correct number of cells
while preserving column bounds and truncation behavior.
In
`@Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/PaneMapTileView.swift`:
- Around line 18-21: Update selectedSurface in PaneMapTileView so an invalid
selectedSurfaceID returns nil instead of falling back to pane.surfaces.first.
Preserve the existing first-surface behavior only when no selection ID is set;
alternatively, reconcile selectedSurfaceIDsByPaneID during layout changes so
stale IDs are removed before rendering and overlay lookups.
In
`@Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/PanesTabsPreviewHost.swift`:
- Around line 7-15: Move PanesTabsPreviewHost and its preview-only fixture
constants and routing into the package’s dedicated Debug area, keeping
production Sources/ APIs free of test/debug seams. Preserve the existing preview
behavior and surface identifiers after relocating the complete fixture behind
that single debug boundary.
In
`@Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/SurfaceDeckBar.swift`:
- Around line 118-123: Expose the localized agent state in the accessibility
semantics for both the SurfaceDeckBar chip at
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/SurfaceDeckBar.swift#L118-L123
and the PaneMapTileView tile at
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/PaneMapTileView.swift#L74-L78.
Reuse the existing localized state text and append it to each view’s
accessibility label or value while preserving their current semantics.
In
`@Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView.swift`:
- Around line 146-167: Update the pane-map presentation state around the
fullScreenCover and workspace.layout dependency so isPaneMapPresented is cleared
whenever workspace.layout becomes nil, including after refresh or reconnect.
Preserve the existing PaneMapOverlay construction when a layout is available,
and ensure the dismissal is owned by the surrounding WorkspaceDetailView state
rather than leaving an empty cover presented.
🪄 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: cbc2a3cf-5ccf-450d-89b4-8b5d2c8a96a0
📒 Files selected for processing (8)
Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+PaneMapPreviews.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/PaneMapOverlay.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/PaneMapPreviewRenderer.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/PaneMapTileView.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/PanesTabsPreviewHost.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/SurfaceDeckBar.swiftPackages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView.swiftPackages/iOS/CmuxMobileShellUI/Tests/CmuxMobileShellUITests/PaneMapPreviewRendererTests.swift
…t compilation Two @suite declarations named PaneMapReorderStateTests existed in the same test target: the original in PaneMapCollectionLayoutEngineTests.swift and the revision-monotonicity suite added later as a dedicated file. Every hosted ios-simulator job failed with "invalid redeclaration of 'PaneMapReorderStateTests'" before reaching a single test. Merge the four reorder tests from the embedded copy into the dedicated file, move the misfiled PaneMapSelectionArbitration test into its own PaneMapSelectionArbitrationTests suite, and drop the embedded copy. No test is removed; the target compiles again. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…removed API The main merge replaced MobileStartupConnectionCoordinator's connectInjectedAttach with the claim/finish lifecycle, and DogfoodAttachPreparationTests already covers that lifecycle end to end. The stale file kept the whole CmuxMobileShellUITests target from compiling, so no package UI suite could run in CI. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Live dogfood findings (Aziz hit both on device, 2026-08-05 ~20:33; reproduced and diagnosed from the Mac side):
Environment is repaired: fresh Mac instance (no churn), web API up, phone re-paired with a readiness receipt at HEAD 7cbecf3. |
|
Diagnosis of the on-device breakage Aziz reported (reproduced twice on an isolated simulator, crash reports in hand): 1. P0 crash: opening a workspace fatals in SwiftUI navigation. 2. P1 stale iroh route after Mac app restarts. Each tagged-Mac relaunch mints a new iroh node; when the relaunch happens while the dev web API (device registry) is down, the registry keeps advertising the dead endpoint and the phone redials it forever ( Note on evidence gap: the hosted pane-map UI tests run against the Dogfood is blocked on the P0 fix. |
…avigation The panes fixture (CMUX_UITEST_PANES_PREVIEW) hosts the pane-zoom pair standalone, so it never caught the P0 crash that killed the production shell: opening a workspace whose sync carries a pane layout mounts the zoom navigation inside the compact stack's pushed destination, and the window's shared NavigationAuthority dies reconciling the two typed paths (SwiftUI.AnyNavigationPath.Error.comparisonTypeMismatch in NavigationColumnState.boundPathChange). Every crash also wipes onboarding and Stack auth state. Teach the mock sync host to serve a two-pane split layout for workspace-main and drive the real shell through every on-device trigger: the initial workspace push, repeated pane-map round-trips, popping back to the list, and pushing a layout-free workspace afterwards (which crashed against the stale pane column even after the pane workspace was popped). Red until the un-nesting fix lands in the next commit. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…hells Opening a pane-layout workspace in the real compact shell fataled within seconds: PaneZoomNavigationStack nested a second typed NavigationStack ([PaneZoomPresentationState.Endpoint]) inside the shell stack's pushed destination ([MobileWorkspacePreview.ID]). Both stacks join the window's shared NavigationAuthority, whose flush queue reconciles one stack's bound path against the other stack's column and force-try!s AnyNavigationPath.Error.comparisonTypeMismatch in NavigationColumnState.boundPathChange. Type-erasing the nested path to NavigationPath was verified NOT to fix it: the misrouted comparison still traps, just deferred to the next push (even of a layout-free workspace, against the popped pane stack's stale column). The standalone fixture never crashed because it has no ancestor stack, which is why the hosted UI tests missed this. Replace the component with PaneZoomHost, which picks the mechanism by an explicit hosting mode passed from each shell call site: - navigationPush (workspaces tab, notifications, notification search): the pane map renders directly in the ancestor stack's destination and the terminal is a zoom fullScreenCover carrying its own root-only NavigationStack. Presentations own a separate navigation context, so nothing joins the ancestor's authority. - column (split detail, regular width): the documented stack-in-a-column pattern is kept unchanged, terminal pushed over the pane-map root. PaneZoomPresentationState's routing API is untouched. The zoom transition moves into the component so both hostings anchor it to the same matchedTransitionSource tiles. WorkspaceDetailView's sheets, dialogs, and the connection-recovery banner now attach to both the base layer and the terminal cover behind opposite visibility gates (binding gates, not structural branches, so layer flips never change subtree identity); a covered base cannot present, and exactly one attachment is live at a time. The panes fixture mirrors the shell's size-class policy so each device family exercises the hosting the production shell uses. Verified on the real-shell repro (sim, live Mac): unpatched build crashed within 15s of opening the workspace; this build survives the open, repeated pane-map zoom round-trips, popping to the list, and switching workspaces with zero new crash reports. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Fixed the P0 crash that killed the app seconds after opening a pane-layout workspace in the real shell (and wiped onboarding plus Stack auth on every hit). Root cause: Fix (afb4b6c): Regression coverage (b38947c, committed red-first): the mock sync host can now serve a two-pane layout for Verified on the live-Mac sim repro: the unpatched build crashed within 15 s of opening "iOS panes dogfood"; this build survived the open, three zoom round-trips, and workspace switches with zero new crash reports. Dictionary: NavigationAuthority is SwiftUI's private per-window bookkeeper that applies every NavigationStack path change; bound path is the array a NavigationStack mirrors through its |
| actionCapabilities: subscription.actionCapabilities | ||
| ) | ||
| return true | ||
| } |
There was a problem hiding this comment.
Reorder response wipes workspace list
High Severity
applyAuthoritativeWorkspaceMutationResponse applies the pane-reorder RPC payload through applyRemoteWorkspaceList without mergeExistingWorkspaces, and the secondary-Mac path replaces that Mac's entire workspacesByMac entry. The Mac returns a workspace-scoped list (it still has workspace_id), so a successful reorder can drop every other workspace and clear group sections when groups is present and empty.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit afb4b6c. Configure here.
| // pre-mutation layout; retiring its authority makes it drop on | ||
| // arrival instead of rolling this response back. | ||
| noteForegroundAuthoritativeWorkspaceListApplied() | ||
| return true |
There was a problem hiding this comment.
Stale sync can undo reorder
Medium Severity
The new authority generation only retires in-flight legacy mobile.workspace.list fetches. A successful pane reorder applies the RPC list into the UI without updating stateSyncMirror, so an overlapping state-sync fetch or projection can still publish the pre-mutation layout and snap the map back after the drag succeeded.
Additional Locations (2)
Reviewed by Cursor Bugbot for commit afb4b6c. Configure here.
| hasUnread = try container.decode(Bool.self, forKey: .hasUnread) | ||
| sortIndex = try container.decode(Int.self, forKey: .sortIndex) | ||
| terminals = try container.decode([Terminal].self, forKey: .terminals) | ||
| layout = try container.decodeIfPresent(MobileWorkspaceLayout.self, forKey: .layout) |
There was a problem hiding this comment.
Sync layout decode fails closed
Medium Severity
WorkspaceSyncRecord decodes layout with decodeIfPresent, which still throws when the key is present but malformed. The RPC workspace list intentionally uses try? so unknown or bad layouts become nil without failing the row. A newer Mac layout shape on the state-sync path can therefore fail the whole sync payload instead of degrading to layout-less rows.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit afb4b6c. Configure here.
The regression drive-through asserted the tile-tap return within waitForPaneMap's default 4 seconds. With the terminal hosted as a full-screen cover, returning from the pane map re-presents the cover and rebuilds the terminal subtree, which takes 6-10 seconds on a saturated CI runner; the first green run recorded only these two timing misses while the app survived the entire drive. The test guards liveness, not latency, so widen the return waits to 15 seconds and the map-open waits to 8. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…osting With the terminal hosted as a full-screen cover, the pane-map bar stays mounted underneath and iOS keeps its items in the accessibility tree, so the shared back button, title menu, and changes chip existed twice (once per bar). VoiceOver could reach covered controls and identifier-based automation failed with "multiple matching elements", which is how the fixture UI test caught it. Gate every pane-map toolbar item with accessibilityHidden while the cover is presented, in the workspace detail and the panes fixture; column hosting is untouched because its endpoints swap by navigation instead of covering. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The cover re-presentation rebuilds the terminal subtree, and the CI simulator VM takes 15-18 seconds for it where the old stack pop restored a retained view instantly; the green run showed the deck reappearing about 18 seconds after the tile tap while the 15-second wait had already expired. Raise waitForPaneMap's default to 20 seconds (wait-until, so fast runs are unchanged; negative checks keep their explicit short timeouts), and anchor the regression test's return check on the surface deck's reappearance, which lives inside the terminal presentation and avoids entangling the liveness check with cover toolbar hit-testing. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Per-item accessibilityHidden inside ToolbarItem content does not remove
the covered bar's items from the accessibility tree on the iOS 26.4
simulator runtime, so the shared back button still matched twice under
the terminal cover ("multiple matching elements" in the fixture and
regression runs). Hide the base's whole navigation bar while the cover
is presented, centralized in PaneZoomHost's push hosting; the bar is
invisible under the cover anyway and returns the moment the terminal
starts unzooming. This supersedes the per-item gates, which are
reverted.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Toolbar items inside a presented full-screen cover never report hittable to XCUI on the CI simulator runtime even though real taps land (the shared tap helper already falls back to frame coordinates for that). The inactive side of waitForPaneMap gated on the terminal utilities button's hittability, so every return-to-terminal wait expired on CI while the app was demonstrably healthy. Keep the hittability requirement for the pane-map side, whose bar is a regular navigation bar, and accept existence plus pane-map-chrome absence as the terminal endpoint signal. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Green run 31167770677 passed both zoom round-trips and the pop to the list, then the workspace-docs row tap left the runner on the list: the row was tapped while the popped pane workspace's cover was still tearing down, so the push never started. Retry the row tap once when the row is demonstrably still on screen after the terminal wait expires; the terminal-surface assertion stays strict. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
There are 4 total unresolved issues (including 3 from previous reviews).
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 51bf1ab. Configure here.
| // workspace's cover finishes tearing down on a slow runner; | ||
| // retry once if we are demonstrably still on the list. | ||
| docsRow.tap() | ||
| } |
There was a problem hiding this comment.
Retry uses exists, not hittable
Medium Severity
The retry treats docsRow.exists as proof the list is still showing, but NavigationStack keeps prior rows in the hierarchy after a successful push. A slow MobileTerminalSurface appearance can then fire a second tap against a non-hittable row and fail this P0 crash regression on saturated CI.
Reviewed by Cursor Bugbot for commit 51bf1ab. Configure here.
|
Follow-ups on the crash fix, all pushed:
Open item: Dictionary: cover means the fullScreenCover presentation hosting the zoomed terminal; re-presentation is SwiftUI presenting that cover again after it was dismissed to show the pane map; hit-testability (isHittable) is XCUI's check that an element can receive a synthesized tap at its hit point. |
|
Red/green proof is complete: the regression test passes on head in run 31176397846 ( |
… out The waitForPaneMap timeout is the only failure signal CI keeps (the xcresult is not uploaded), so a red run never says which of the three conditions blocked. Print the condition values and the accessibility tree on timeout. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… level With the terminal hosted as a full-screen cover, the pane map stays mounted underneath and SwiftUI's subtree accessibilityHidden is not honored for it: the AX dump at the failing waits showed the overlay marker, collection, tiles, and tab strips all still queryable while the cover was up, so assistive tech saw the covered map and every identifier-based wait for the terminal return timed out. The collection container now sets accessibilityElementsHidden from the map's visibility (the reliable UIKit flag), and the overlay marker swaps to a Covered identifier so presentation queries cannot match it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Bugbot is paused — on-demand spend limit reachedBugbot 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. |
…o terminal Both docs-row taps land with the app alive, yet MobileTerminalSurface never appears, so the push after pane-workspace teardown fails without a crash. CI keeps no xcresult; print the tree so the swallowed push is attributable to the list, an interposed layer, or an empty detail. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Takeover round (Claude, sole writer this session): the pane XCUITests were red on every runner because the covered pane map never left the accessibility tree. With the terminal hosted as a full-screen cover, SwiftUI's subtree Fix in 38a06df: the collection container now sets Evidence at head 059ed21, iphone family, selected-test results inside runs whose overall conclusion is polluted by the pre-existing lint baseline and
Watch item: at 38a06df one real-shell run failed the trailing docs-workspace push with both row taps landing and the app alive (https://github.com/manaflow-ai/cmux/actions/runs/31698953494); it did not reproduce at 059ed21. The new dump will attribute the next occurrence. Environment status: no fresh device or simulator build of this head exists yet. The cloud fleet had no free slot for over 20 minutes and the local Mac was saturated (load ~65) by an unrelated media pipeline, so the visual re-verification of the cold-open transition under the cover architecture is pending; structurally the map is now the mounted base in push hosting, so the original unloaded-root cut cannot occur there. Aziz is unreachable and the install queue is armed. The branch is currently CONFLICTING with main and needs a merge before the next dogfood build. |
|
CI status on head 059ed21 (dispatch https://github.com/manaflow-ai/cmux/actions/runs/32057272028, filter |


Summary
workspace.layout.v1contract and add the authoritativeworkspace.pane_reorder.v1mutation path.Mechanism
The iOS pane map keeps a single optimistic
PaneMapReorderStatesnapshot per request.MobileShellCompositesends the requested pane order through the shared workspace mutation client, then refreshes from the authoritative Mac layout. A failed or mismatched response restores the prior snapshot and exposes the localized error.The Mac owner-routed handler validates the requested pane IDs against the workspace, reorders full pane contents through the shared terminal-controller move path, installs placeholders during the transaction, rolls back on failure, and publishes exactly one final mobile layout without stealing focus.
PaneZoomPresentationStateowns a workspace-local native navigation path initialized to.terminal. Pane-map and terminal endpoints therefore share one stack and toolbar, use the same matched zoom source, remain interruptible in both directions, and restore the prior focused surface on the first presented frame. Compact workspace navigation owns tab-bar visibility while a workspace is pushed.PaneMapSelectionArbitrationabsorbs movement before selection resolution. UIKit collection drag/drop owns relayout; a tap is accepted only after the gesture resolves without scroll or drag movement.Verification
PaneMapCollectionLayoutEngineTests,PaneMapReorderStateTests,PaneZoomPresentationStateTests, and both pane-map XCUITest flows).cmux-unitresult: 25/25 passed, including authoritative publication, full-pane moves, stale-topology rejection, and failure reconciliation.Package.resolvedpolicy, pbxproj test wiring, localization JSON parsing, andgit diff --checkpassed.alphaterminal with no pane-map flash or primary tab bar.cmux DEV panessucceeded from pushed HEAD and is running on LG HDR 4K with the persisted three-pane dogfood workspace.Device status / residual risk
Aziz became unavailable to CoreDevice before the final two physical tests and optimized reinstall could be repeated. The final signed build and physical rerun remain pending only on the phone reconnecting/unlocking; simulator and prior physical recording evidence are complete.
Dictionary
Note
Medium Risk
Touches workspace sync, mutation reconciliation, and terminal replay side channels; race handling is explicit but layout + reorder paths are user-visible and cross Mac/iOS contract boundaries.
Overview
Adds end-to-end pane topology on mobile: workspaces can carry a synced
layout(splits, panes, surface tabs) through state sync, RPC list decoding, andMobileWorkspacePreview, with topology hashing that ignores divider ratios and titles so drags don’t spam invalidation.Pane map visuals build on shared terminal render-grid code:
paneMapPreviewresolves styled cells and wide glyphs; VT replay reusesresolvedCharacterCellWidths. The shell fetches read-onlymobile.terminal.replaywithgrid_only(bounded concurrency) for map thumbnails without resizing the Mac grid.Mutations and races:
workspace.pane.reorder(capabilityworkspace.pane_reorder.v1) applies the RPC’s authoritative workspace list immediately; a foreground list authority generation drops stale in-flightmobile.workspace.listsnapshots so optimistic UI can’t roll back a successful reorder. Opening a workspace picks the focused pane’s ready terminal (with sensible fallbacks).UI layer in this diff adds pane-map collection layout,
PaneMapOverlay, surface deck / toolbar reserves for pane-map controls, DEBUG panes-tabs preview + autoplay, and connection-recovery overlay gating so it doesn’t double-draw under terminal covers.CI runs CmuxMobileRPC package tests.
Reviewed by Cursor Bugbot for commit 51bf1ab. Bugbot is set up for automated code reviews on this repo. Configure here.