Skip to content

iOS: add pane-grouped deck and Safari-style pane map - #8188

Closed
azooz2003-bit wants to merge 89 commits into
mainfrom
feat-ios-panes-ux
Closed

azooz2003-bit wants to merge 89 commits into
mainfrom
feat-ios-panes-ux

Conversation

@azooz2003-bit

@azooz2003-bit azooz2003-bit commented Jul 15, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

  • Publish pane topology through the existing shared workspace.layout.v1 contract and add the authoritative workspace.pane_reorder.v1 mutation path.
  • Route iPhone drag-to-relayout to the paired Mac, move complete pane contents on macOS, publish one reconciled layout, and roll back optimistic iOS order with a localized failure state when the mutation fails.
  • Replace the separate full-screen pane presentation with one workspace-local native navigation stack. It starts with the prior focused terminal already installed, so entering a workspace never flashes the pane map first.
  • Keep one shared toolbar across terminal and pane-map endpoints. Back, title, menu, and disappearing items participate in the same native zoom transition.
  • Use native collection-view drag/drop plus explicit gesture arbitration so scroll, swipe, drag, and absorbed movement cannot resolve as pane selection; only an intentional resolved tap focuses a pane.
  • Preserve Safari-style split ratios, card sizing, tab grouping, live previews, and older-Mac fallback behavior.
  • Localize the reorder failure in English and Japanese.

Mechanism

The iOS pane map keeps a single optimistic PaneMapReorderState snapshot per request. MobileShellComposite sends 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.

PaneZoomPresentationState owns 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.

PaneMapSelectionArbitration absorbs 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

  • Final focused iOS simulator result: 15/15 passed (PaneMapCollectionLayoutEngineTests, PaneMapReorderStateTests, PaneZoomPresentationStateTests, and both pane-map XCUITest flows).
  • Mobile terminal preview regression suite: 9/9 passed.
  • Final focused macOS cmux-unit result: 25/25 passed, including authoritative publication, full-pane moves, stale-topology rejection, and failure reconciliation.
  • Workspace grouping, Package.resolved policy, pbxproj test wiring, localization JSON parsing, and git diff --check passed.
  • The broad iOS convention lint still reports its existing repository-wide baseline; no new finding originates in the final routing follow-up.
  • Both supplied recordings were extracted and inspected frame by frame. A new real paired-simulator recording confirms workspace entry goes directly to the restored alpha terminal with no pane-map flash or primary tab bar.
  • Physical Aziz dogfood recording was inspected across all 508 frames: map/deck correlation, swipe absorption, drag reorder, intentional tap focus, partial-edge interruption/cancel, map return, and terminal return completed without the prior exit ghost.
  • Physical XCTest before the assertion-marker stabilization: 1 passed, 1 failed only because the marker center was covered after the behavior completed. The assertion was stabilized afterward and passes in the final simulator run.
  • Final tagged macOS build cmux DEV panes succeeded 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

  • Authoritative reconciliation means iOS replaces its optimistic order with the Mac-confirmed topology after each mutation.
  • Gesture absorption means movement claims the interaction so it cannot later become a tap.

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, and MobileWorkspacePreview, 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: paneMapPreview resolves styled cells and wide glyphs; VT replay reuses resolvedCharacterCellWidths. The shell fetches read-only mobile.terminal.replay with grid_only (bounded concurrency) for map thumbnails without resizing the Mac grid.

Mutations and races: workspace.pane.reorder (capability workspace.pane_reorder.v1) applies the RPC’s authoritative workspace list immediately; a foreground list authority generation drops stale in-flight mobile.workspace.list snapshots 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.

cmux reload-cloud and others added 6 commits July 15, 2026 14:00
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>
@coderabbitai

coderabbitai Bot commented Jul 15, 2026 •

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It 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 reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The 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.

Changes

Workspace layout transport and models

Layer / File(s) Summary
Wire contract and host serialization
Packages/Shared/CMUXMobileCore/..., Sources/Mobile/..., Sources/TerminalController+MobileWorkspaceList.swift
Workspace records and mobile workspace payloads now include recursive layout data, surface metadata, capability advertising, and topology-aware hashing.
iOS decoding and layout mapping
Packages/iOS/CmuxMobileRPC/..., Packages/iOS/CmuxMobileShellModel/...
Remote layouts decode into public split-pane models with traversal, surface lookup, normalized rectangle, ratio-clamping, and unknown-type fallback support.
Terminal preview rendering
Packages/Shared/CMUXMobileCore/Sources/CMUXMobileCore/MobileTerminalRenderGrid*
Render-grid previews resolve grapheme widths and paint overlapping and wide glyphs into bounded preview rows.
Transport and model tests
Packages/Shared/CMUXMobileCore/Tests/..., Packages/iOS/CmuxMobileRPC/Tests/..., Packages/iOS/CmuxMobileShellModel/Tests/..., cmuxTests/...
Tests cover wire encoding, malformed layout tolerance, normalized geometry, topology hashing, state deltas, layout serialization, and preview rendering.

Surface Deck and pane-map experience

Layer / File(s) Summary
Preview retrieval
Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+PaneMapPreviews.swift
RPC helpers fetch individual terminal replay grids and batch deduplicated requests with at most four concurrent tasks.
Surface Deck and pane map
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/SurfaceDeck*, Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/PaneMap*
The Surface Deck groups pane surfaces, exposes creation and selection actions, and the pane map renders normalized tiles, previews, tabs, agent indicators, refresh, and terminal navigation.
Workspace integration and validation
Packages/iOS/CmuxMobileShellUI/Sources/..., ios/cmuxUITests/..., ios/cmux/Resources/Localizable.xcstrings
Workspace detail presents the Surface Deck and pane map, replaces terminal-picker toolbar flows with utilities controls, and updates localized strings and UI tests.

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
Loading

Possibly related PRs

Suggested reviewers: lawrencecchen, austinywang


Important

Pre-merge checks failed

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

❌ Failed checks (4 errors, 1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Cmux Algorithmic Complexity ❌ Error PaneMapValue.reconciledSurfaceIDs rescans each pane.surfaces up to 3 times with contains/first in a UI update path; no size bound or benchmark was shown. Cache a Set of surface IDs per pane or do one-pass selection validation so current, phone-selected, and pane-selected lookups reuse the same scan.
Cmux Swift @Concurrent ❌ Error MobileShellComposite.fetchPaneMapPreviewGrid is @MainActor yet performs RPC sendRequest and decode work without an explicit background hop/@Concurrent boundary. Capture remoteClient on MainActor, then move request/decoding into a nonisolated @concurrent helper or detached task; keep only actor-state access on MainActor.
Cmux Swiftui State Layout ❌ Error FAIL: The PR adds layout-affecting GeometryReader/onGeometryChange usage in PaneMapOverlay, PaneMapTileView, and WorkspaceDetailView, which the rule forbids. Move geometry sizing out of render-time state/layout decisions (e.g. use Layout or preference-based measurement) and avoid geometry-driven resizing/positioning in these views.
Cmux No Test Or Debug Seam In Production Source ❌ Error lastSummaryHash was widened from private to private(set) in production code so tests can read it, and the fidelity test does so directly. Keep lastSummaryHash private and assert via emitted workspace.updated behavior or a test-owned sink/poll; avoid widening production visibility for tests.
Docstring Coverage ⚠️ Warning Docstring coverage is 6.38% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Cmux User-Facing Error Privacy ❓ Inconclusive placeholder pending evidence
✅ Passed checks (19 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Cmux Swift Actor Isolation ✅ Passed PASS: New models are plain top-level value types; the only new @MainActor API lands on the already-@mainactor MobileShellComposite, with explicit await hops and no new Sendable reference debt.
Cmux Swift Blocking Runtime ✅ Passed PASS: Production diff only changes observer hash visibility and removes a test counter; no new semaphores, locks, sync waits, or sleeps in Sources/. Task.sleep is test-only.
Cmux Browser Automation Off-Main ✅ Passed No diff touched TerminalController or ControlCommandExecutionPolicy; the PR’s browser-related changes are UI shell work, not browser socket automation routing.
Cmux Expensive Synchronous Load ✅ Passed Edited production Swift files add in-memory layout serialization and async preview fetches; no new RestorableAgentSessionIndex.load(), JSONL scan, or main-actor history load appears.
Cmux Cache Substitution Correctness ✅ Passed layout snapshots come from live bonsplitController.treeSnapshot(); the only cache is transient preview text and is gated by notificationID+createdAt freshness checks.
Cmux No Hacky Sleeps ✅ Passed The only non-Swift build/runtime change is the iOS workflow, and it uses simctl bootstatus plus a launch-failure retry—no fixed sleep/timer/polling hacks were added.
Cmux Swift Concurrency ✅ Passed Diff adds only structured Task/TaskGroup usage; the detached decode task is awaited/cancelled, and no new DispatchQueue/completion-handler or app-state Combine patterns appear.
Cmux Swift Package Boundaries ✅ Passed Core pane/layout models live in SwiftPM packages; app-target additions are observer and transport glue, not standalone reusable domain logic.
Cmux Swiftpm Lockfiles ✅ Passed HEAD commit only changes two Swift files; no Package.swift, .gitignore, Xcode project, or Package.resolved files were touched, so the lockfile rule isn’t implicated.
Cmux Swift Logging ✅ Passed No added logging lines appear in the PR diff; existing logger/debug-only calls were preexisting or DEBUG-gated.
Cmux Full Internationalization ✅ Passed All added user-facing Swift strings route through L10n, and every new xcstrings key has both en and ja entries; no production literal-copy regressions found.
Cmux Architecture Rethink ✅ Passed Production changes keep layout/revision ownership in Workspace with explicit invariants; no new repair-path sleeps/polling or split owners, and remaining sleeps are test-only.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PASS: The diff only adds/changes SwiftUI views, sheets, and a fullScreenCover; no user-visible NSWindow/NSPanel/Window/WindowGroup scenes or cmux.* identifier changes.
Cmux Source Artifacts ✅ Passed Changed paths are source, tests, config, localization, or removals only; no logs, caches, build output, screenshots, or scratch dirs appeared.
Cmux No Ambient Global State ✅ Passed The only production hunk is lastSummaryHash visibility; no new file-scope API, mutable globals, or singleton-style state was added.
Title check ✅ Passed The title clearly summarizes the main iOS changes: a pane-grouped deck and Safari-style pane map.
Description check ✅ Passed The description thoroughly covers the changes, implementation, testing, residual risks, and UI behavior, despite omitting some template headings and a video link.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat-ios-panes-ux

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

❤️ Share

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

@greptile-apps

greptile-apps Bot commented Jul 15, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This 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 NavigationStack that starts on the focused terminal (no pane-map flash on entry). It ships the workspace.layout.v1 topology publication through the existing state-sync contract and adds the workspace.pane_reorder.v1 mutation path with optimistic iOS ordering reconciled against the Mac's confirmed result.

  • Mac side (Workspace+MobilePaneReorder, Workspace+WorkspaceSurfaceTreeReading): full-pane-content moves with placeholder tabs, topology-hash-gated publication, and a suppression counter that keeps intermediate Bonsplit delegate callbacks quiet during multi-step transactions.
  • iOS side (PaneMapReorderState, PaneMapCollectionView, PaneZoomPresentationState): a single-pending-request optimistic state machine, UIKit drag/drop owning relayout with PaneMapSelectionArbitration absorbing scroll/drag before tap resolution, and a NavigationStack-based zoom transition shared between pane-map and terminal endpoints.
  • Localization: all new user-facing strings (pane map, surface deck, reorder failure) are translated into English and Japanese.

Confidence Score: 4/5

Safe to merge after the failure-path focus restore in applyMobilePaneOrder is addressed; the rest of the reorder, navigation, and localization logic is sound.

The Mac-side applyMobilePaneOrder calls bonsplitController.focusPane for every pane inside the selection-restoration loop (to set context for selectTab), leaving bonsplit's focusedPaneId on the last spatial pane after the loop. The explicit re-focus to the originally captured focused pane lives only on the success branch — guard mutationSucceeded else { return false } exits before it runs. After a failed pane reorder the bonsplit focusedPaneId is wrong, and the next topology hash computed by any Bonsplit delegate callback will embed the incorrect focused pane and publish it to iOS as the focusedOnMac indicator. Everything else — the optimistic state machine, gesture arbitration, NavigationStack routing, serializer, authorization, and localization — is well-structured and consistent with the existing codebase patterns.

Files Needing Attention: Sources/Workspace+MobilePaneReorder.swift — the failure-path branch inside applyMobilePaneOrder needs to restore originallyFocusedPaneID before returning false.

Important Files Changed

Filename Overview
Sources/Workspace+MobilePaneReorder.swift New file implementing full-pane-content reorder on the Mac. Suppression/restore logic is structurally correct, but the failure path skips restoring the originally focused pane, leaving bonsplit's focusedPaneId on the last spatial pane after a rollback.
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/PaneMapReorderState.swift New optimistic-reorder state machine. The reconcile/complete/rollback state transitions are correct; finishSuccessfulMutationAfterAuthoritativeRefresh is a legitimate internal helper called from both reconcile and complete.
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/PaneZoomPresentationState.swift New workspace-local navigation state. The terminal-first initial path is correctly set and presentation transitions are handled cleanly. PaneZoomNavigationStack uses a binding to propagate back-swipe into the state struct.
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/PaneMapCollectionView.swift New UIKit collection view bridging drag/drop, selection arbitration, and reorder state. The mutation-pending guard on drag-begin, the optimistic item update, and the async reorder task look correct.
Sources/TerminalController+WorkspaceMove.swift Adds v2MobileWorkspacePaneReorder handler: validates pane IDs, runs the reorder on the main actor, and returns either an error or the updated workspace list. Follows the pattern of existing mutation handlers.
Sources/Workspace+WorkspaceSurfaceTreeReading.swift Adds publishMobilePaneLayoutRevisionIfChanged using a topology hash to gate publication. Suppression count correctly blocks intermediate delegate callbacks during multi-step Bonsplit mutations.
Packages/macOS/CmuxWorkspaces/Sources/CmuxWorkspaces/Mobile/MobileWorkspaceLayoutSerializer.swift New serializer converting a bonsplit external tree to the shared MobileWorkspaceLayout DTO. Properly instantiable struct (not static-only); depth-first traversal is correct.
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView.swift Replaces full-screen pane presentation with a workspace-local NavigationStack. The terminal-first path and layout-nil onChange guard handle workspace layout availability transitions correctly.
ios/cmux/Resources/Localizable.xcstrings All new UI strings (pane map, surface deck, reorder failure) include both English and Japanese translations. String keys follow the existing mobile.* naming convention.

Sequence Diagram

sequenceDiagram
    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
Loading

Reviews (18): Last reviewed commit: "fix(ios): prefer focused pane terminal o..." | Re-trigger Greptile

Comment thread Sources/TerminalController+MobileWorkspaceList.swift Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 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

📥 Commits

Reviewing files that changed from the base of the PR and between d91d836 and ce3e3a6.

⛔ Files ignored due to path filters (1)
  • ios/cmux.xcworkspace/xcshareddata/swiftpm/Package.resolved is excluded by !**/Package.resolved
📒 Files selected for processing (41)
  • Packages/iOS/CmuxMobileRPC/Sources/CmuxMobileRPC/MobileSyncWorkspaceListResponse.swift
  • Packages/iOS/CmuxMobileRPC/Sources/CmuxMobileRPC/MobileWorkspacePreview+RemoteMapping.swift
  • Packages/iOS/CmuxMobileRPC/Tests/CmuxMobileRPCTests/MobilePaneLayoutDecodeTests.swift
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+PaneMapPreviews.swift
  • Packages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobilePaneLayout.swift
  • Packages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobilePaneNode.swift
  • Packages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobilePaneSplit.swift
  • Packages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobilePaneSplitOrientation.swift
  • Packages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobilePaneSurface.swift
  • Packages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobilePaneSurfaceType.swift
  • Packages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobileWorkspacePreview.swift
  • Packages/iOS/CmuxMobileShellModel/Tests/CmuxMobileShellModelTests/MobilePaneLayoutTests.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/CMUXMobileRootView.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/ChatAgentStateKind.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/PaneMapOverlay.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/PaneMapPreviewRenderer.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/PaneMapTileView.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/PaneMapValue.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/PanesTabsPreviewHost.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/SurfaceDeckActions.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/SurfaceDeckBar.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/SurfaceDeckValue.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TerminalPickerMenu.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TerminalPickerMenuActions.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TerminalPickerMenuDiagnostics.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TerminalPickerMenuValue.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailCreateDelayedTerminalPreviewView.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView+AgentChat.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView+MenuState.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceUtilitiesMenu.swift
  • Packages/iOS/CmuxMobileShellUI/Tests/CmuxMobileShellUITests/PaneMapPreviewRendererTests.swift
  • Packages/iOS/CmuxMobileShellUI/Tests/CmuxMobileShellUITests/SurfaceDeckValueTests.swift
  • Packages/iOS/CmuxMobileShellUI/Tests/CmuxMobileShellUITests/TerminalPickerMenuValueTests.swift
  • Packages/iOS/CmuxMobileSupport/Sources/CmuxMobileSupport/UITestConfig.swift
  • Sources/Mobile/MobileHostService+Capabilities.swift
  • Sources/Mobile/MobileWorkspaceListObserver.swift
  • Sources/TerminalController+MobileWorkspaceList.swift
  • cmuxTests/MobileWorkspaceListFidelityTests.swift
  • ios/cmux/Resources/Localizable.xcstrings
  • ios/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

Comment thread Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/PaneMapOverlay.swift Outdated
Comment thread Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/PaneMapTileView.swift Outdated
cmux reload-cloud and others added 2 commits July 17, 2026 00:12
# 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>
@cursor

cursor Bot commented Jul 17, 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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView.swift (1)

145-165: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Dismiss the pane map when its required layout disappears.

isPaneMapPresented can remain true while workspace.layout becomes nil after 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

📥 Commits

Reviewing files that changed from the base of the PR and between ce3e3a6 and 29e9f20.

📒 Files selected for processing (3)
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/CMUXMobileRootView.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView.swift
  • Packages/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>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (6)
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/PanesTabsPreviewHost.swift (1)

7-15: 📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

Isolate 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 dedicated Debug/ 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 win

Dismiss the pane map when its required layout disappears.

isPaneMapPresented can remain true while workspace.layout becomes nil after 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 win

Use the span cell width when placing row text.

PaneMapPreviewRenderer.rows walks span.text one grapheme at a time, but MobileTerminalRenderGridFrame.RowSpan already carries cellWidth. 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 win

Make 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 win

Use String.localizedStringWithFormat for locale-aware number formatting.

Using String(format:) bypasses locale-aware number formatting for the %d placeholders (such as localized grouping separators or digits). Based on learnings, when providing arguments to a plain localized string, prefer String.localizedStringWithFormat to 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 win

Don't fall back to pane.surfaces.first for an invalid selected surface.

A stale selectedSurfaceID can make this tile render the first surface while PaneMapOverlay still uses the original ID for preview and agent lookups, so the UI can show mismatched data. As per path instructions, reconcile selectedSurfaceIDsByPaneID when the layout changes, or return nil here 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

📥 Commits

Reviewing files that changed from the base of the PR and between 29e9f20 and 0891f8b.

📒 Files selected for processing (8)
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+PaneMapPreviews.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/PaneMapOverlay.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/PaneMapPreviewRenderer.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/PaneMapTileView.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/PanesTabsPreviewHost.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/SurfaceDeckBar.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView.swift
  • Packages/iOS/CmuxMobileShellUI/Tests/CmuxMobileShellUITests/PaneMapPreviewRendererTests.swift

azooz2003-bit and others added 3 commits August 5, 2026 16:19
…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>
@azooz2003-bit

Copy link
Copy Markdown
Collaborator Author

Live dogfood findings (Aziz hit both on device, 2026-08-05 ~20:33; reproduced and diagnosed from the Mac side):

  1. Ghost screen when the connection dies during pane-map entry (P1 for this PR's bar). With the paired Mac gone, opening workspace detail then the pane view left a black screen whose only rendered element was the alt-screen notice toolbar item — no content, no chrome, no recovery affordance. The connection-recovery overlay should own this state; a transition begun against a dead stream must not strand a bare toolbar item.
  2. Disconnected fallback is unrecognizable. After backing out, navigation landed in DisconnectedWorkspaceShellView — a bare "Workspaces" list with no tab bar, no status, no reconnect affordance visible in the screenshot. Needs a visible disconnected banner/spinner.
  3. Mac-side attach retry storm from a restored hibernated tab. After session restore, the not-yet-instantiated alpha-notes surface looped surface.attach.defer reason=noWindow several times per second indefinitely (398+ entries), flooding the debug log and plausibly starving the mobile host RPCs (phone attach timed out during the storm). Interacting with the surface once (hydrating it) stopped the loop. Likely interaction: pane-map preview replay requests repeatedly poking a surface that can never attach without a window.
  4. The tagged Mac app appears to exit after its web API becomes unreachable. Three occurrences, each with auth: auth.phase=access_token timed out after 60.0 seconds in the final log window and no crash report. A dev build should degrade, not exit, when token refresh fails. Needs a targeted repro before fixing.

Environment is repaired: fresh Mac instance (no churn), web API up, phone re-paired with a readiness receipt at HEAD 7cbecf3.

@azooz2003-bit

Copy link
Copy Markdown
Collaborator Author

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. SwiftUI/NavigationColumnState.swift:684: Fatal error: 'try!' ... AnyNavigationPath.Error.comparisonTypeMismatch, faulting in NavigationColumnState.boundPathChange(to:) via NavigationAuthority.flushRequestQueue(). Two .ips reports (21:31, 21:58). The second fired seconds after a plain workspace open on a healthy connection, so this is not connection-dependent. Root-cause hypothesis: WorkspaceDetailView mounts the nested PaneZoomNavigationStack (typed path [PaneZoomPresentationState.Endpoint], initial [.terminal]) inside a navigationDestination of the compact shell stack (typed path [MobileWorkspacePreview.ID]); the window's navigation authority routes the inner stack's bound-path change to the outer column and the element-type comparison traps. Fix direction: stop nesting two typed stacks — either fold the pane/terminal endpoints into the outer stack using a heterogeneous NavigationPath, or drive the zoom presentation without a second NavigationStack. Every crash also resets onboarding + Stack auth state, which produced the "suddenly at sign-in/onboarding/bare Workspaces" states.

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 (pairing route failed kind=iroh ...: requestTimedOut every ~10s). The terminal endpoint then decays to a bare centered exclamationmark.triangle.fill with no recovery affordance (same placeholder Aziz screenshotted). The phone auto-recovered the moment a fresh Mac instance registered against a live registry. Fix directions: re-register on registry reachability, make the phone fall back to re-fetching the registry when all stored routes time out repeatedly, and give the decayed endpoint a visible reconnect state.

Note on evidence gap: the hosted pane-map UI tests run against the CMUX_UITEST_PANES_PREVIEW fixture host, which mounts PaneZoomNavigationStack standalone — the real nested arrangement was never exercised end-to-end, which is how the P0 crash shipped. A regression test needs to drive the real shell (mock host, no fixture bypass).

Dogfood is blocked on the P0 fix.

azooz2003-bit and others added 2 commits August 6, 2026 21:24
…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>
@azooz2003-bit

Copy link
Copy Markdown
Collaborator Author

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: PaneZoomNavigationStack nested a second typed NavigationStack ([PaneZoomPresentationState.Endpoint]) inside the compact shell stack's pushed workspace destination ([MobileWorkspacePreview.ID]). Both stacks register with the window's shared NavigationAuthority; its flush queue reconciles one stack's bound path against the other stack's column state and force-try!s SwiftUI.AnyNavigationPath.Error.comparisonTypeMismatch in NavigationColumnState.boundPathChange. I verified on the sim repro that type-erasing the nested path to NavigationPath does not fix it: the misrouted comparison still traps on the next push, even of a layout-free workspace, against the popped pane stack's stale column. The panes fixture never crashed because it hosts the pair without an ancestor stack, which is why the hosted UI tests missed this.

Fix (afb4b6c): PaneZoomHost now picks the navigation mechanism from an explicit hosting mode passed by each shell call site. Under pushed shells (workspaces tab, notifications, notification search) the pane map renders directly in the ancestor stack's destination and the terminal is a zoom fullScreenCover with its own root-only stack, so nothing joins the ancestor's authority; presentations own a separate navigation context by design. The split detail keeps the documented stack-in-a-column pattern unchanged. The zoom transition moved into the component so both hostings anchor to the same matchedTransitionSource tiles, and the workspace detail's sheets, dialogs, and recovery banner attach to both layers behind opposite binding gates because a covered base cannot present. PaneZoomPresentationState's routing API is unchanged.

Regression coverage (b38947c, committed red-first): the mock sync host can now serve a two-pane layout for workspace-main, and cmuxUITests/testWorkspacePaneLayoutNavigationSurvivesInRealShell drives the production shell through every on-device trigger: initial push, pane-map round-trips, pop to list, then pushing a layout-free workspace. CI proof: red run on the test-only commit, green run on head.

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. CMUXMobileCore 368/368 green; CmuxMobileShell green except MobileMacConnectionPoolTests, which fails identically on pristine origin/main (pre-existing flake, package untouched here).

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 path: binding; hosting mode is the new explicit switch between presentation-based and column-based pane zoom.

actionCapabilities: subscription.actionCapabilities
)
return true
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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)
Fix in Cursor Fix in Web

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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)
Fix in Cursor Fix in Web

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)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit afb4b6c. Configure here.

azooz2003-bit and others added 6 commits August 6, 2026 22:39
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>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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).

Fix All in Cursor

❌ 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()
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 51bf1ab. Configure here.

@azooz2003-bit

Copy link
Copy Markdown
Collaborator Author

Follow-ups on the crash fix, all pushed:

  • CI red/green proof, corrected run links (the links in my previous comment pointed at dispatches that failed checkout on short SHAs): the test-only commit crashed the app under the new regression test in run 31149003065 (Test Case testWorkspacePaneLayoutNavigationSurvivesInRealShell failed followed by Restarting after unexpected exit, crash, or test timeout). Every post-fix run shows the app surviving the full drive: open, repeated zoom round-trips, pop to list, re-push. No post-fix run contains a crash.
  • 00bf152: with the terminal hosted as a cover, the covered pane-map bar's items stayed in the accessibility tree on the iOS 26.4 runtime, so the shared back button and title menu existed twice (VoiceOver could reach covered controls; automation failed with "multiple matching elements"). The covered bar is now hidden outright while the cover is up, centralized in PaneZoomHost; per-item accessibilityHidden was verified insufficient on that runtime and reverted.
  • 61c2901, 2d0fc11, 7386aa1, 51bf1ab: test-side hardening for the CI simulator VM, where the cover re-presentation rebuilds the terminal subtree in 15-18 s (the old stack pop restored a retained view instantly). waitForPaneMap now defaults to 20 s and only requires hit-testability on the pane-map side, because toolbar items inside a presented cover never report hittable to XCUI on that runtime even though real taps land.

Open item: testPanesPreviewCorrelatesDeckGroupsWithMapRegionsAndSwitchesTabs (fixture host) still fails on the CI runner at the pane-map Done-button return (run 31171860885); the map stays up after the Done tap there. The equivalent return path via tile taps passes on CI and both paths work on a local simulator, so this is either Done-button tap delivery on the saturated runner or a cover re-presentation edge specific to that flow; it needs one focused follow-up before merge. Dogfood note: returning from the pane map now re-presents and rebuilds the terminal instead of restoring a retained pushed view, so watch the map-to-terminal return latency on device.

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.

@azooz2003-bit

Copy link
Copy Markdown
Collaborator Author

Red/green proof is complete: the regression test passes on head in run 31176397846 (ios-simulator (iphone) green, cmuxUITests/cmuxUITests/testWorkspacePaneLayoutNavigationSurvivesInRealShell), against the crash on the test-only commit in run 31149003065. The other failing jobs in these dispatch runs (package-conventions-lint, MobilePairingFailureTests in mobile-core-package) fail identically on the test-only commit and on pristine main, so they are pre-existing branch and runner state, not this change. The fixture Done-button return remains the one open CI item from my previous comment.

azooz2003-bit and others added 3 commits August 13, 2026 00:01
… 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>
@cursor

cursor Bot commented Aug 13, 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.

…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>
@azooz2003-bit

Copy link
Copy Markdown
Collaborator Author

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 accessibilityHidden is not honored for the mounted map underneath: the AX dump captured at the failing wait showed the overlay marker, collection, tiles, and tab strips all still queryable while the cover was up, while the bar-level .toolbar(.hidden) gate held. That also means assistive tech saw the covered map in production.

Fix in 38a06df: the collection container now sets accessibilityElementsHidden from the map's visibility (the UIKit flag is honored across the cover boundary), and the overlay marker swaps to MobilePaneMapOverlayCovered while covered so presentation queries cannot match it. waitForPaneMap and the post-pane docs push now print the AX tree on timeout (b27745f, 059ed21), so a future red run names the blocking element instead of only timing out.

Evidence at head 059ed21, iphone family, selected-test results inside runs whose overall conclusion is polluted by the pre-existing lint baseline and MobilePairingFailureTests flake:

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.

@azooz2003-bit

Copy link
Copy Markdown
Collaborator Author

CI status on head 059ed21 (dispatch https://github.com/manaflow-ai/cmux/actions/runs/32057272028, filter cmuxUITests/cmuxUITests/testPanesPreviewReordersCardsAndZoomsTerminalInBothDirections): both ios-simulator legs (iphone, ipad) passed. The ios-tests gate is red only because package-conventions-lint fails, and that lint fails identically on current main (same free-function/namespace-enum offenders in files this branch does not touch; the one flagged file the branch edits, WorkspaceShellView.swift, carries the same flagged enum on main). The earlier mobile-core-package failures (TerminalViewportResyncTests, MobileShellForegroundResumeTests, MobileShellRenderGridInputCatchUpTests, TerminalOutputDeliveryQueueTests) reproduce locally at the merge-base in unchanged test files, so they are pre-existing too. The CmuxMobileRPC suite this branch adds to CI passes 3/3 full local runs.

@lawrencecchen lawrencecchen added the stale-revisit Closed after 30+ days without activity; preserved for possible revisit or reopening. label Sep 23, 2026
@github-project-automation github-project-automation Bot moved this from Todo to Done in cmux backlog Sep 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

stale-revisit Closed after 30+ days without activity; preserved for possible revisit or reopening.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants