Skip to content

iOS workspace list: divider cleanup, + long-press group create, principled drag-drop - #7671

Merged
azooz2003-bit merged 20 commits into
mainfrom
feat-remove-workspace-dividers
Jul 11, 2026
Merged

azooz2003-bit merged 20 commits into
mainfrom
feat-remove-workspace-dividers

Conversation

@azooz2003-bit

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

Copy link
Copy Markdown
Collaborator

Four iOS workspace-list changes from one dogfood session.

Indent guides removed. Grouped rows drew a 1px leading line and WorkspaceGroupFooterRow drew an end-of-group corner mark; both removed. The footer stays as an invisible 12pt drop target (drag semantics + accessibility unchanged), and defaultMinListRowHeight is overridden so the iOS List minimum (~44pt) no longer inflates it into a dead band after each group.

Long-press "+" creates a workspace group. Tap keeps creating a workspace; hold opens a menu with New Workspace / New Workspace Group. New capability-gated RPC workspace.group.create (host advertises workspace.group_create.v1; trimmed optional title, blank falls back to the desktop's localized auto-name; anchor created without stealing Mac focus). Old clients byte-identical; over-cap host files edited at net-zero lines; en+ja strings in the iOS catalog.

Principled drag-drop policy (Mac parity). All drag rules now live in MobileWorkspaceMovePolicy, mirroring the macOS sidebar resolver/planner: group-header drops normalize to whole-group boundaries (groups can never nest or interleave, matching the host's mobileWorkspaceMoveTopLevelBeforeID normalization), pinned tiers clamp at top level and within groups (anchor first, pinned members, then unpinned), anchors cannot leave or reorder within their group, and footer/collapsed/end/stale-ID slots are explicit rules. A test-target-independent host-order simulator (reimplemented from the host code, with line citations) sweeps every source row × destination across fixtures proving predicted order == host order.

Continuous drag haptics fixed. Root cause: a successful move RPC cleared the optimistic order before the authoritative snapshot arrived, snapping rows back under the active reorder interaction (continuous UIKit retarget haptics); the pending flag also toggled moveDisabled mid-gesture. Moves now reconcile on snapshot via a pure tested reconciler (keep optimism until the authoritative order matches or supersedes; roll back only on failure), reorder stays enabled while pending, and pipelined sends are chained so the Mac applies rapid drags in UI order.

109 CmuxMobileShellModel tests (incl. the independent parity oracle); structured review + policy checks clean.

🤖 Generated with Claude Code


Note

Medium Risk
Touches Mac-scoped RPC auth, workspace ordering semantics, and pipelined optimistic UI state; regressions could mis-order workspaces or reject valid group creates, but behavior is heavily covered by new parity and shell tests.

Overview
Adds creating workspace groups from iOS via capability workspace.group_create.v1 and RPC workspace.group.create (optional trimmed title, Mac auto-name when blank), wired through shell/RPC auth like other Mac-scoped mutations; the + control becomes a menu (tap = new workspace, menu item = new group) with failure toasts and localization.

Reorder/drag is centralized in MobileWorkspaceMovePolicy so list drags normalize like the Mac sidebar (group boundaries, pinned tiers, anchor rules, end-of-group slots). List rendering now emits group footers only after expanded groups with visible member rows; the footer is an invisible drop slot (guides removed). Optimistic ordering uses MobileWorkspaceOptimisticOrderReconciler so the list does not snap back before the host snapshot; moves can pipeline (chained RPCs, epoch abort on failure/supersede, cap of 3) while reorder stays enabled during pending moves; moveWorkspace reports success/failure to the UI layer.

Mac host advertises the capability, requires attach tickets for workspace.group.create, and implements v2MobileWorkspaceGroupCreate returning an updated workspace list.

Reviewed by Cursor Bugbot for commit 3281bc9. Bugbot is set up for automated code reviews on this repo. Configure here.

@vercel

vercel Bot commented Jul 8, 2026 •

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
cmux Ready Ready Preview, Comment Jul 11, 2026 7:37am
cmux-staging Building Building Preview, Comment Jul 11, 2026 7:37am

@coderabbitai

coderabbitai Bot commented Jul 8, 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

This PR removes visual divider elements from the workspace list UI in the iOS shell. The leading overlay divider on indented workspace rows is removed, and the drawn end-of-group marks in the group footer row are replaced with an invisible spacer, while existing insets and accessibility behavior remain unchanged.

Changes

Remove list divider marks

Layer / File(s) Summary
Remove leading divider overlay and footer marks
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceListView.swift, Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceGroupFooterRow.swift
Removes the conditional leading overlay divider (1pt vertical Rectangle) shown on indented rows, and replaces the drawn end-of-group ZStack marks in the footer row with an invisible Color.clear spacer of fixed height, keeping row insets, drop target, and accessibility behavior intact.

Estimated code review effort: 1 (Trivial) | ~5 minutes

🚥 Pre-merge checks | ✅ 24 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description summarizes the changes well, but it omits the template's Testing, Demo Video, Review Trigger, and Checklist sections. Add the missing template sections with testing details, a demo video link or note, the review-trigger comment block, and the checklist items.
✅ Passed checks (24 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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 Diff only removes SwiftUI chrome in two View types; no actor isolation, Sendable, or background-access changes were introduced.
Cmux Swift Blocking Runtime ✅ Passed Diff only removes SwiftUI hairlines in two iOS views; no semaphores, waits, sleeps, syncs, or locks were added or expanded.
Cmux Browser Automation Off-Main ✅ Passed PR only changes two iOS UI files; no edits in TerminalController or ControlCommandExecutionPolicy, so the browser-automation off-main rule isn’t implicated.
Cmux Expensive Synchronous Load ✅ Passed Diff only removes iOS SwiftUI divider chrome; it adds no RestorableAgentSessionIndex.load(), JSON/JSONL parsing, or agent-history access on main/UI paths.
Cmux Cache Substitution Correctness ✅ Passed Diff only removes visual divider/corner marks in two SwiftUI list rows; no fresh-read-to-cache substitution or persistence/history/snapshot logic changed.
Cmux No Hacky Sleeps ✅ Passed Patch only touches two Swift UI files; no TS/JS/shell/build runtime code or new sleep/timer/polling logic is introduced.
Cmux Algorithmic Complexity ✅ Passed The diff only removes UI chrome in two SwiftUI views; it adds no new scans, sorts, or per-item rescans on scalable collections.
Cmux Swift Concurrency ✅ Passed Diff only removes SwiftUI divider decorations; no new legacy async patterns or concurrency expansions were introduced.
Cmux Swift @Concurrent ✅ Passed PASS: The only edits are synchronous SwiftUI layout changes; no async helpers, actor hops, or @concurrent annotations were added or altered.
Cmux Swift File And Package Boundaries ✅ Passed PASS: Only tiny UI-only edits in a 34-line FooterRow and existing 638-line WorkspaceListView (4 insertions/20 deletions); no new mixed-responsibility or package-boundary issues.
Cmux Swiftpm Lockfiles ✅ Passed PR only changes iOS SwiftUI sources; no Package.swift, Package.resolved, Xcode project, .gitignore, or workflow/dependency changes trigger the lockfile rule.
Cmux Swift Logging ✅ Passed The only touched Swift files are two iOS UI views, and neither adds or changes print/debugPrint/dump/NSLog/Logger usage.
Cmux User-Facing Error Privacy ✅ Passed The PR only removes mobile divider drawings; it adds no user-facing error/alert/copy, and the remaining accessibility text is generic and non-sensitive.
Cmux Full Internationalization ✅ Passed Only visual/layout SwiftUI changes; no user-facing text or locale assets were added or altered, and existing localized accessibility strings were untouched.
Cmux Swiftui State Layout ✅ Passed Diff only removes decorative SwiftUI chrome; it adds no new state, GeometryReader, or store-backed row subtree patterns.
Cmux Architecture Rethink ✅ Passed Local iOS UI chrome removal only; clear spacer preserves drop target/a11y, and no timing, observers, or duplicate owners were introduced.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PASS: PR only edits iOS list-row views; no NSWindow/NSPanel/WindowGroup changes or cmuxAuxiliaryWindowIdentifiers ownership issues.
Cmux Source Artifacts ✅ Passed Both changed paths are hand-written Swift source; the diff only removes iOS chrome markers and adds no artifact, temp, cache, build, or checkout paths.
Cmux No Test Or Debug Seam In Production Source ✅ Passed Touched production Swift files only remove UI chrome; no DEBUG/test-only accessor, wrapper, or widened test seam was added.
Cmux No Ambient Global State ✅ Passed Diff only removes iOS view chrome inside existing structs; no new file-scope funcs, mutable globals, or singletons were added.
Title check ✅ Passed The title clearly covers the main changes: divider cleanup, group creation, and drag-drop behavior.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat-remove-workspace-dividers

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 8, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

Four workspace-list improvements: indent-guide chrome removed, long-press + creates a workspace group via new capability-gated RPC workspace.group.create, drag-drop policy centralized in MobileWorkspaceMovePolicy (mirroring Mac sidebar normalization), and optimistic reorder fixed by keeping predicted order through pipelined moves via MobileWorkspaceOptimisticOrderReconciler.

  • Group create: capability workspace.group_create.v1 gates the new RPC end-to-end — iOS checks the flag before sending, Mac host verifies the attach ticket the same way as workspace.group.action, and the handler uses the existing v2MainSync pattern without stealing focus.
  • Drag policy: all move-normalization rules (group boundary clamping, pin-tier ordering, anchor restrictions, footer slot semantics) now live in one tested struct; MobileWorkspaceOrderMoveApplier delegates to it, eliminating the duplicate path.
  • Pipelining fix: moves chain as Task<Bool, Never> so the Mac applies them in UI order; an epoch counter aborts queued moves when a host supersede or failure invalidates their predictions, and moveWorkspace now returns Bool so failed moves roll back without disabling reorder mid-gesture.

Confidence Score: 5/5

Safe to merge; the group-create RPC is capability-gated and ticket-authed identically to existing Mac-scoped mutations, and the pipelining rewrite is backed by 109 tests including a host-order parity oracle.

All new types are pure value-type Sendable structs with no actor isolation concerns. The reconciler and policy logic are well-tested against the host simulator. The three nits (O(n²) scan in matches, ternary dispatch in the switch arm, and duplicate computed properties) are non-blocking and don't affect correctness at any realistic workspace count.

MobileWorkspaceOptimisticOrder.swift — the entries.first(where:) linear scan inside matches() is worth converting to a dictionary before the workspace list grows larger.

Important Files Changed

Filename Overview
Packages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobileWorkspaceMovePolicy.swift New 471-line file centralizing all drag-drop policy (group boundary normalization, pin-tier clamping, anchor restrictions). Logic mirrors Mac sidebar resolver; well-structured with O(n) dictionary-backed helpers throughout.
Packages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobileWorkspaceOptimisticOrder.swift New optimistic-order snapshot type. matches() has an O(n²) linear scan (entries.first(where:) inside a loop over authoritative) that runs on the main actor per host snapshot; otherwise the materialization logic is correct and handles new/deleted workspaces gracefully.
Packages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobileWorkspaceOptimisticOrderReconciler.swift New reconciler correctly keeps optimism while host snapshots match pending-base intermediates and clears on failure or supersede. Logic is pure and well-tested.
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceListView+DragDrop.swift Drag-drop rework adds pipelined sends (chained Task<Bool,Never>), epoch-based abort, and correct count tracking. The predecessor-failure and epoch-check paths are both correct; count is balanced across all exit branches.
Sources/TerminalController+WorkspaceGroupAction.swift New v2MobileWorkspaceGroupCreate handler follows the same v2MainSync pattern as v2MobileWorkspaceGroupAction; ticket auth added consistently in MobileHostService.swift. One style nit: dispatch switch uses a ternary instead of two separate case arms.
Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+WorkspaceActions.swift New createWorkspaceGroup method correctly gates on workspace.group_create.v1 capability, trims the optional title, and routes through the existing sendWorkspaceMutation path with correct auth checks.
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceListView+Actions.swift Tap-to-create-workspace preserved as primary action; long-press menu adds 'New Workspace Group' only when capability is advertised. Correct use of SwiftUI Menu primaryAction.
Packages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobileWorkspaceListItem.swift Footer-emission refactored to memberRowsInCurrentRun counter with flushGroupFooter(). Anchor-only groups now emit no slot (correct per spec); populated groups emit exactly one footer. All edge cases covered by updated tests.
ios/cmux/Resources/Localizable.xcstrings Two new string keys added with both en and ja translations, matching the catalog's full locale set.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant UI as WorkspaceListView
    participant Rec as OptimisticOrderReconciler
    participant Chain as Task Chain
    participant Shell as MobileShellComposite
    participant RPC as MobileCoreRPCClient
    participant Mac as MobileHostService

    UI->>Rec: Build Reconciler with optimisticOrder and pendingBases
    UI->>Chain: pendingWorkspaceMoveTask chained on previousMove
    Chain->>RPC: workspace.move with attach ticket
    RPC->>Mac: ticketAuthorizationResultIfNeeded check
    Mac-->>Chain: result Bool
    Chain->>UI: pendingWorkspaceMoveCount decrement

    UI->>Rec: reconciling on onChange
    alt authoritative matches optimisticOrder
        Rec-->>UI: clear optimism fulfilled
    else authoritative matches a pendingBase
        Rec-->>UI: keep optimisticOrder prune older bases
    else no match supersede or failure
        Rec-->>UI: clear bump epoch detach chain
    end

    Note over UI,Mac: Group create flow
    UI->>Shell: createWorkspaceGroup with optional title
    Shell->>Shell: check workspace.group_create.v1 capability
    Shell->>RPC: workspace.group.create
    RPC->>Mac: requiresCurrentAttachTicket check
    Mac-->>Shell: Result success or failure
    Shell-->>UI: handleWorkspaceActionResult createWorkspaceGroup
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant UI as WorkspaceListView
    participant Rec as OptimisticOrderReconciler
    participant Chain as Task Chain
    participant Shell as MobileShellComposite
    participant RPC as MobileCoreRPCClient
    participant Mac as MobileHostService

    UI->>Rec: Build Reconciler with optimisticOrder and pendingBases
    UI->>Chain: pendingWorkspaceMoveTask chained on previousMove
    Chain->>RPC: workspace.move with attach ticket
    RPC->>Mac: ticketAuthorizationResultIfNeeded check
    Mac-->>Chain: result Bool
    Chain->>UI: pendingWorkspaceMoveCount decrement

    UI->>Rec: reconciling on onChange
    alt authoritative matches optimisticOrder
        Rec-->>UI: clear optimism fulfilled
    else authoritative matches a pendingBase
        Rec-->>UI: keep optimisticOrder prune older bases
    else no match supersede or failure
        Rec-->>UI: clear bump epoch detach chain
    end

    Note over UI,Mac: Group create flow
    UI->>Shell: createWorkspaceGroup with optional title
    Shell->>Shell: check workspace.group_create.v1 capability
    Shell->>RPC: workspace.group.create
    RPC->>Mac: requiresCurrentAttachTicket check
    Mac-->>Shell: Result success or failure
    Shell-->>UI: handleWorkspaceActionResult createWorkspaceGroup
Loading

Reviews (20): Last reviewed commit: "Merge remote-tracking branch 'origin/mai..." | Re-trigger Greptile

Comment thread Sources/Workspace.swift Outdated
Comment on lines 2642 to 2643
splitDividerHex: splitDividerHex
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Configured Divider Color Can Be Ignored

When paneBorderColorHex is set, this call now leaves borderHex on the derived separator color and puts the configured value only in splitDividerHex. If the vendored bonsplit renderer still reads borderHex for split dividers on any path, the user setting is silently ignored and split panes render with the default separator color instead of the configured color.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

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

Inline comments:
In `@vendor/bonsplit`:
- Line 1: The bonsplit submodule pointer is being updated to a SHA that is only
reachable from origin/feat-split-divider-color, so update the bonsplit submodule
commit first by landing it on origin/main and then bump the parent repo pointer.
Make sure the submodule update references the bonsplit submodule commit/checkout
flow rather than directly pinning the parent to the orphaned SHA, so the new
submodule commit is reachable from origin/main before the pointer change.
🪄 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: 8a5ded02-7bdc-4bf2-b930-dcddb72a0c82

📥 Commits

Reviewing files that changed from the base of the PR and between 027c998 and ce1b714.

📒 Files selected for processing (2)
  • Sources/Workspace.swift
  • vendor/bonsplit

Comment thread vendor/bonsplit Outdated
@@ -1 +1 @@
Subproject commit fe53e04c5ec195f5a11f31374368c674769d2630
Subproject commit 82260c92a9ca4025f7c288036a6bcb685e67c11d

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Verify bonsplit submodule commit contains splitDividerHex and is pushed to remote main.

BONSPLIT_DIR="vendor/bonsplit"
NEW_COMMIT="82260c92a9ca4025f7c288036a6bcb685e67c11d"

# Check that splitDividerHex exists in the ChromeColors type
echo "=== Searching for splitDividerHex in bonsplit sources ==="
rg -n 'splitDividerHex' "$BONSPLIT_DIR" --type=swift || echo "WARNING: splitDividerHex not found in bonsplit sources"

# Check that the new commit is an ancestor of origin/main (i.e., pushed to remote)
echo ""
echo "=== Checking remote ancestry ==="
cd "$BONSPLIT_DIR" && git fetch origin 2>/dev/null
if git merge-base --is-ancestor "$NEW_COMMIT" origin/main 2>/dev/null; then
  echo "OK: $NEW_COMMIT is on origin/main"
else
  echo "WARNING: $NEW_COMMIT is NOT on origin/main — the pointer may be orphaned"
fi

# Show the commit message for context
echo ""
echo "=== Commit details ==="
git log -1 --oneline "$NEW_COMMIT" 2>/dev/null || echo "Commit not found locally"

Repository: manaflow-ai/cmux

Length of output: 499


🏁 Script executed:

#!/bin/bash
set -euo pipefail

SUBMODULE=vendor/bonsplit
COMMIT=82260c92a9ca4025f7c288036a6bcb685e67c11d

echo "== Submodule HEAD =="
git -C "$SUBMODULE" rev-parse HEAD
git -C "$SUBMODULE" status --short

echo
echo "== Commit summary =="
git -C "$SUBMODULE" show --stat --oneline --decorate=short --no-renames --summary "$COMMIT" | sed -n '1,120p'

echo
echo "== Tree search for splitDividerHex at commit =="
git -C "$SUBMODULE" grep -n 'splitDividerHex' "$COMMIT" -- || true

echo
echo "== Remote refs containing commit =="
git -C "$SUBMODULE" for-each-ref --format='%(refname:short) %(objectname)' refs/remotes | sed -n '1,120p'
git -C "$SUBMODULE" branch -r --contains "$COMMIT" || true

echo
echo "== origin/main ancestry check =="
git -C "$SUBMODULE" fetch origin >/dev/null 2>&1 || true
if git -C "$SUBMODULE" merge-base --is-ancestor "$COMMIT" origin/main; then
  echo "ancestor-of-origin/main"
else
  echo "not-ancestor-of-origin/main"
fi

Repository: manaflow-ai/cmux

Length of output: 9764


Move the bonsplit submodule commit onto origin/main before bumping this pointer. The new SHA is only reachable from origin/feat-split-divider-color, so the parent repo can end up pinned to an orphaned submodule commit.

🤖 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 `@vendor/bonsplit` at line 1, The bonsplit submodule pointer is being updated
to a SHA that is only reachable from origin/feat-split-divider-color, so update
the bonsplit submodule commit first by landing it on origin/main and then bump
the parent repo pointer. Make sure the submodule update references the bonsplit
submodule commit/checkout flow rather than directly pinning the parent to the
orphaned SHA, so the new submodule commit is reachable from origin/main before
the pointer change.

Comment thread vendor/bonsplit Outdated
@@ -1 +1 @@
Subproject commit fe53e04c5ec195f5a11f31374368c674769d2630
Subproject commit 82260c92a9ca4025f7c288036a6bcb685e67c11d

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P0 Pinned commit is unavailable

The parent app now depends on this bonsplit revision for ChromeColors.splitDividerHex, but the pinned submodule object is not available from the configured vendor/bonsplit remote in this checkout. A clean clone that runs git submodule update --init vendor/bonsplit can fail before the app builds, leaving import Bonsplit and the new splitDividerHex API unresolved. Please point the submodule at a commit that is fetchable from https://github.com/manaflow-ai/bonsplit.git and contains the divider-color changes.

The grouped-workspace rows drew a 1px vertical indent line at their
leading edge, and WorkspaceGroupFooterRow drew an L-shaped end-of-group
corner mark. Both are the dividers the user reported. The footer row
stays as an invisible 12pt spacer so its drag-drop target (drop above =
into group, below = top level) and accessibility element keep working.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@azooz2003-bit
azooz2003-bit force-pushed the feat-remove-workspace-dividers branch from 4070cfe to 43f1fb0 Compare July 9, 2026 02:21
@azooz2003-bit azooz2003-bit changed the title Remove default workspace dividers (sidebar edge + split panes) iOS: remove workspace-list group indent guides Jul 9, 2026
Tap keeps creating a workspace; press-and-hold now opens a menu with
New Workspace and New Workspace Group. Group creation from mobile is a
new RPC: the Mac host advertises workspace.group_create.v1 and handles
workspace.group.create (trimmed optional title; blank falls back to the
desktop's localized auto-name; anchor workspace created without
stealing Mac focus, matching mobile workspace.create). The client
gates the menu item and the send path on the capability plus mac-scoped
mutation authorization, using the same mutation plumbing as the other
group actions; old clients and workspace.group.action are unchanged.
Over-cap host files (TerminalController.swift, MobileHostService.swift)
changed at net-zero lines via extended case labels. New strings have
en+ja entries in the iOS app catalog.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@azooz2003-bit azooz2003-bit changed the title iOS: remove workspace-list group indent guides iOS workspace list: remove group indent guides + long-press + to create a group Jul 9, 2026
The invisible 12pt group-footer drop-target row was inflated to the iOS
List default minimum row height (~44pt), leaving a large blank band
after every group. Override defaultMinListRowHeight on the list so the
footer renders at its actual 12pt; all real rows exceed the floor.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
azooz2003-bit and others added 2 commits July 9, 2026 16:14
All drag rules now live in one policy point (MobileWorkspaceMovePolicy),
mirroring the macOS sidebar resolver/planner semantics: group-header
drops normalize to whole-group boundaries (never interleave another
group, matching the host's mobileWorkspaceMoveTopLevelBeforeID
normalization), pinned tiers clamp at top level and within groups
(anchor first, pinned members, then unpinned), anchors cannot leave or
reorder within their group, footer/collapsed/end slots and stale-ID
cases are explicit rules. The optimistic applier uses the same policy,
so the predicted order always equals the host's post-move order; a
test-target-independent host-order simulator (reimplemented from the
host code, not shared with the policy) sweeps every source row x
destination across fixtures to prove parity.

Haptic churn root cause: a successful move RPC cleared the optimistic
order before the authoritative snapshot arrived, snapping rows back
under the active UIKit reorder interaction (continuous retarget
haptics); the pending flag also toggled moveDisabled mid-gesture. Moves
now reconcile on snapshot (keep optimism until the authoritative order
matches or supersedes; roll back only on a failed move) via a pure
tested reconciler, and reorder stays enabled during pending moves with
pipelined drops based on the prior optimistic order.

109 CmuxMobileShellModel tests pass; CmuxMobileShellUI iOS build green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Review finding: with reorder enabled during pending moves, two in-flight
move RPCs had no ordering guarantee, so a rapid second drag could reach
the Mac first and the authoritative snapshot would diverge from the
predicted order, dropping pipelined optimism mid-gesture. Each move now
chains on the previous send (single tail Task) so the host applies
moves in UI order, and the pending flag is a counter so overlapping
moves cannot clear each other's pending state early.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@blacksmith-sh

This comment has been minimized.

azooz2003-bit and others added 2 commits July 9, 2026 17:01
Policy check flagged the reconciler's static methods as
static-as-namespace; the snapshots are now constructor-injected state
and the order-signature factory lives on MobileWorkspaceOrderSignature,
the type that owns it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
check-package-resolved-policy.py rejects this location; it was created
by an xcodebuild verification run, not a resolution change.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@azooz2003-bit azooz2003-bit changed the title iOS workspace list: remove group indent guides + long-press + to create a group iOS workspace list: divider cleanup, + long-press group create, principled drag-drop Jul 10, 2026
Review P1s: the serial send chain had no size bound (a slow or offline
Mac let drags enqueue unbounded work), and a chained move could not see
its predecessor's outcome, so an intent computed against a rejected
prediction was still sent and produced a wrong final order. The chain
now carries acceptance (Task<Bool, Never>): a successor aborts without
sending when any predecessor failed (the failure rollback already
restored the authoritative order), and reorder pauses once 8 moves are
pending, a depth normal round-trips never reach.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
azooz2003-bit and others added 3 commits July 9, 2026 19:58
Dogfood follow-up: the flap pile lived in stacks of header-only groups,
whose 12pt footers created adjacent razor-thin reorder targets, but the
end-of-group slot is still wanted for populated groups so both boundary
targets are directly draggable. items() now emits the slot only for
expanded groups with at least one member row (16pt, invisible,
per-group unique even for non-contiguous runs); dropping before it
joins the group at its end (members and external workspaces alike) and
dropping after it lands at root. Empty and collapsed groups stay
slot-free — their below-header gap already joins/roots — so the header
stacks that flapped have no thin targets at all. The round-5
membership-inference rule remains as the documented fallback for
boundaries without a slot.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Review P1: a failed move left its completed false-valued task as the
chain tail, so every later drag chained on it and silently aborted
forever (and left fresh optimistic state visible). The failure handler
now detaches the tail: queued dependents still hold their captured
reference and drain by aborting, while drags started after the failure
chain on nothing and proceed normally.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
azooz2003-bit and others added 4 commits July 9, 2026 20:22
Judge findings: the identity-drop test's source index selected the new
end-of-group slot (rejected by the footer-source guard before the
identity check ran), leaving identity rejection untested; it now drags
a real row into its own gap, with a separately named footer-source
rejection test and restored collapsed-group no-slot coverage. The
items() doc bullet now states the actual end-of-group slot emission
rule.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Two review findings on pipelined optimism. First, the reconciler only
remembered the latest move's base, so a three-deep drag chain snapped
through intermediate orders when the first refresh landed; every move
now records its source order and a snapshot matching any pending
intermediate keeps the displayed prediction (pruning strictly older
bases), the displayed order drains the chain, an unmatched order
supersedes, and failure clears everything. Second, held optimism froze
full row snapshots; optimistic state now stores only workspace IDs plus
intended membership and every render overlays the current authoritative
content, so titles, unread state, capabilities, collapse/expand,
deletions, and arrivals stay live mid-chain.

120 model tests including the three-move chain, out-of-order supersede,
mid-chain failure, and live-content scenarios.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Review findings: the optimistic entries dropped pin state, so a
Mac-side workspace or group pin change mid-chain could clamp the move
to a no-op while the stale base still matched, pinning an invalid order
on screen; predictions now capture workspace and group pin state and
any change supersedes optimism. The move policy also rebuilt its group
lookup dictionaries on every access inside per-workspace loops; they
are now built once at init. The anchor-only-group drop-slot finding is
consciously rejected: a synthetic slot there recreates the flapping
thin-row pile in header stacks that started this work, every other row
still joins via the below-header gap, and only the row directly beneath
an empty group needs a two-step move.

122 model tests including workspace- and group-pin supersede cases.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Review triage. A supersede cleared optimism but queued dependent RPCs
still sent intents computed against the overruled predictions; the
chain now carries an epoch that a supersede or failure bumps, so
not-yet-sent moves abort. The list-wide reduced minimum row height
(needed for the invisible end-of-group spacer) let group header rows
shrink below the 44pt tap target; header rows now pin an explicit
minimum. The pipeline cap drops from 8 to 3 because every queued move
currently costs a full workspace refresh on reply; applying the
mutation's returned list instead of re-aggregating is noted as a
follow-up. Consciously accepted: a workspace arriving mid-chain may
transiently render outside its pin tier until the chain drains or a
supersede corrects it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
azooz2003-bit and others added 2 commits July 10, 2026 07:37
Native onMove is index-only, so the group/root boundary could only be
encoded as invisible rows: too thin to hit (the root target was
practically unreachable under a finger) or too thick not to read as
dead space. The gesture layer is now onDrag/onDrop with a pure
MobileWorkspaceDropResolver porting the Mac resolver contract: hit row
plus top/middle/bottom bands decide the slot, a horizontal lane at a
group's end boundary picks group (right half) or root (left half),
dropping on a header's middle band appends into that group including
anchor-only groups, and group drags are offered whole-group boundaries
only. A single overlay draws the policy-clamped insertion line
(indented for in-group targets) or a header highlight, so the
destination is visible before the drop; haptics fire once per semantic
target change. All synthetic footer rows and the min-row-height
override are deleted; intents flow into the unchanged policy,
optimistic-chain, and epoch pipeline through one shared commit path.

131 model tests (11 new resolver rules; parity oracle sweep intact).
Drag feel, autoscroll cadence, and context-menu coexistence need device
dogfood; iPad pointer and VoiceOver reorder are follow-ups.

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.

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 3281bc9. Configure here.

// reference and drain by aborting above.
pendingWorkspaceMoveTask = nil
}
return accepted

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

In-flight move ignores epoch

Medium Severity

Pipelined drag tasks only compare workspaceMoveEpoch before awaiting the move RPC. When syncOptimisticWorkspaceOrder clears optimism and bumps the epoch (matching snapshot or supersede), tasks already awaiting moveWorkspace still complete and can send a move computed against an invalidated prediction, so the Mac may apply a stale reorder after the list already converged.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 3281bc9. Configure here.

@azooz2003-bit
azooz2003-bit merged commit 14e658b into main Jul 11, 2026
36 of 38 checks passed
@azooz2003-bit
azooz2003-bit deleted the feat-remove-workspace-dividers branch July 11, 2026 05:12

This branch was successfully deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant