Skip to content

iOS: launch agent workspaces from the task composer - #7670

Merged
azooz2003-bit merged 202 commits into
mainfrom
feat-ios-task-composer
Jul 20, 2026
Merged

azooz2003-bit merged 202 commits into
mainfrom
feat-ios-task-composer

Conversation

@lawrencecchen

@lawrencecchen lawrencecchen commented Jul 8, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Adds an iOS task composer that launches Claude, Codex, OpenCode, or Shell in a new workspace with an explicit Mac and working directory.
  • Makes the prompt card the primary surface. Agent selection lives inside it through a native picker with an Edit Agents entry, while a compact Mac-to-folder card shows the launch route below.
  • Stages launch feedback as Preparing workspace and Starting , then selects the exact workspace returned by workspace.create.
  • Adds hierarchical directory browsing with Home, Computer, parent navigation, pagination recovery, search, and visible access failures.
  • Adds CMUX Labs to Settings only in DEBUG-compiled builds. Shell Icon Lab keeps 12 persistent A-L treatments available for ongoing experiments, while Release always uses the canonical Shell treatment.
  • Supports Dynamic Type and Reduce Motion in the redesigned composer.
  • Incorporates the iOS reconnect and build-isolation fix from commit 01cf217.

Verification

  • Package build and full iOS app/UI-test build-for-testing passed at head 7b2b247.
  • Six focused composer UI tests passed with zero failures. The affected native-menu and accessibility XXXL tests passed again after the final Dynamic Type patch.
  • Debug and Release Labs gate tests passed, 15/15 and 14/14. Release contains no CMUX Labs entry and resolves the canonical Shell icon.
  • Tagged macOS cloud reload passed in Actions run 29639651656. Tagged iOS Simulator reload passed from the same commit, signed in, paired, and connected.
  • Real Codex launch passed with the prompt Reply exactly: Spark launch verified. The created workspace received the exact prompt and returned Spark launch verified.
  • Launch-transition video inspection found no blank or stale intermediate surface.
  • English and Japanese localization coverage passed for the new user-facing strings.

Evidence

  • Screenshots, launch video, contact sheets, and frame slices: /Users/abdulazizalbahar/Dev/Manaflow/cmux-assets/feat-ios-task-composer/
  • Final UI-test result: /Users/abdulazizalbahar/Library/Developer/Xcode/DerivedData/cmux-spark-ios/Logs/Test/Test-cmux-ios-2026.07.18_03-27-11--0700.xcresult

Checklist

  • I tested the change locally
  • I added or updated tests for behavior changes
  • I localized new user-facing strings in English and Japanese
  • Dogfood approval received

Dogfood approval is required before merge.

A bottom-right floating plus button on the workspace list opens a New Task
composer: type a prompt, pick a task template (Claude/Codex/Shell seeded,
user-editable with SF Symbol or emoji icons), pick the target Mac, and set
the working directory. Submit creates a workspace on that Mac via the
existing workspace.create RPC, now sending title, working_directory,
initial_command, and initial_env from iOS.

Prompt injection is a pure composer: {prompt} placeholder or appended
shell-quoted argument, plus CMUX_TASK_PROMPT in the environment for
multi-line scripts. Templates persist device-locally in a UserDefaults
JSON store behind MobileTaskTemplateStoring. The Mac now expands a
leading ~ in working_directory.
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@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 15, 2026 11:29am
cmux-staging Building Building Preview, Comment Jul 15, 2026 11:29am

@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

Adds mobile task composer support with template models, persistence, prompt-based command composition, workspace-create routing, SwiftUI UI, localization, and app wiring. Separately updates terminal workspace creation to normalize and expand working-directory inputs.

Changes

Mobile Task Composer feature

Layer / File(s) Summary
Task template data and composition
Packages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/*, Packages/iOS/CmuxMobileShellModel/Tests/CmuxMobileShellModelTests/*
Adds task templates, prompt-derived command composition, submission snapshots and identities, agent icon metadata, and model tests.
Task template persistence and session state
Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileTaskTemplate*, Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite.swift, Packages/iOS/CmuxMobileShell/Tests/CmuxMobileShellTests/MobileTaskTemplateStoreTests.swift
Adds UserDefaults-backed template and draft persistence, last-used values, sign-out cleanup, and session-generation guards.
Workspace-create contracts and pinned execution
Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileWorkspaceCreateSpec.swift, Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+WorkspaceCreateRequest.swift, Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+TaskComposer.swift, Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/WorkspaceCreatePinnedContext.swift
Threads optional task specifications through workspace creation, validates target Mac capabilities using pinned connection context, and maps request failures.
Routing infrastructure and submission validation
Packages/iOS/CmuxMobileShell/Tests/CmuxMobileShellTests/ComposerSubmitRouting*, Packages/iOS/CmuxMobileShell/Tests/CmuxMobileShellTests/MobileTaskComposerSubmitTests.swift, Packages/iOS/CmuxMobileShell/Tests/CmuxMobileShellTests/MobileShellWorkspaceCreateTests.swift, Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+RouteSelection.swift
Extends routing fixtures and assertions for capabilities, request payloads, stale contexts, retries, cancellation, busy requests, and failure mapping.
Task Composer SwiftUI screens and integration
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/*, Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceShellView*, ios/cmuxPackage/Sources/cmuxFeature/CMUXMobileRootScene.swift, ios/cmux/Resources/Localizable.xcstrings, Packages/iOS/CmuxMobileShellUI/Package.swift
Adds the composer entry point, submission sheet, template management screens, icon assets, draft lifecycle, navigation settlement, localization, and template-store wiring.

Working directory tilde expansion fix

Layer / File(s) Summary
Working directory expansion helper and usage
Sources/TerminalController+WorkspaceCreate.swift, cmuxTests/WorkspaceCreateWorkingDirectoryTests.swift, cmux.xcodeproj/project.pbxproj
Adds normalized tilde expansion for working_directory and cwd, with project registration and tests for blank, absolute, and home-relative paths.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant TaskComposerSheet
  participant MobileShellComposite
  participant RemoteMac
  User->>TaskComposerSheet: Select template, Mac, prompt, and directory
  TaskComposerSheet->>MobileShellComposite: submitTaskComposer(macDeviceID, spec)
  MobileShellComposite->>MobileShellComposite: Switch and validate pinned Mac context
  MobileShellComposite->>RemoteMac: workspace.create
  RemoteMac-->>MobileShellComposite: Create result
  MobileShellComposite-->>TaskComposerSheet: Success or mapped failure
Loading

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs


Important

Pre-merge checks failed

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

❌ Failed checks (5 errors, 2 warnings)

Check name Status Explanation Resolution
Cmux Swift Actor Isolation ❌ Error TaskComposerSheet runs Task { await submit() } and then sync-reads/writes store.taskTemplateStore; the store is @MainActor, so the diff adds a missing isolation boundary. Mark the composer submit/persist helpers @MainActor or wrap the task body in Task { @mainactor in ... }, and keep all taskTemplateStore access on that actor.
Cmux Algorithmic Complexity ❌ Error Template swipe-delete loops IndexSet and each delete rescans/saves the full templates array (TaskComposerSheet.swift:427-430 -> MobileTaskTemplateStore.swift:57-64), making batch deletes O(k*n). Delete all selected IDs in one store call (e.g. Set + single removeAll) or add deleteTemplates(ids:) so the template array is scanned once and lastTemplateID updated once.
Cmux Swiftui State Layout ❌ Error TaskTemplateIcon writes a main-actor image cache inside a helper called from body, which is render-time state mutation. Move brand-image caching out of the render path (or make it a pure lookup/static precompute) so body and its helpers stay side-effect free.
Cmux Source Artifacts ❌ Error ghostty is a submodule gitlink update (5ae712a→a78fe53e), i.e. a dependency checkout, with no deliberate product reason shown. Remove the ghostty pointer change from the PR, or document it as an intentional dependency bump and keep it only if required.
Cmux No Ambient Global State ❌ Error TaskTemplateIcon.swift adds @MainActor private static var brandImageCache, a new shared mutable cache not owned or injected. Move brand-image caching into an injectable image-loading service (or remove caching) and pass it in from the app seam; keep TaskTemplateIcon stateless.
Docstring Coverage ⚠️ Warning Docstring coverage is 19.30% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ⚠️ Warning The description covers summary and testing, but it misses the required Demo Video and Review Trigger sections from the template. Add the Demo Video section with a direct link or attachment and include the copy/paste Review Trigger block; rename Verification to Testing if you want to match the template exactly.
✅ Passed checks (18 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 Blocking Runtime ✅ Passed PASS: The new production Swift paths are async-only with no added semaphores/locks/sync waits; the only delay found is a UI toast auto-dismiss via ContinuousClock.sleep, which is allowed.
Cmux Browser Automation Off-Main ✅ Passed The diff only touches iOS task-composer UI, tests, and localization; it does not change browser.* socket commands, worker routing, or WebKit wait paths.
Cmux Expensive Synchronous Load ✅ Passed Touched task-composer/UI files only; no added RestorableAgentSessionIndex.load(), SharedLiveAgentIndex.shared, or agent-history/transcript/trajectory loads on main/interactive paths.
Cmux Cache Substitution Correctness ✅ Passed Draft/template/Mac restores are freshness-checked against current paired Macs/templates, and session generation guards stale writes; the cached directory read is UI-only.
Cmux No Hacky Sleeps ✅ Passed No changed TypeScript/JavaScript/shell/runtime-script files; the diff is Swift/resources only, so the no-hacky-sleeps rule doesn’t apply.
Cmux Swift Concurrency ✅ Passed No banned legacy async patterns were introduced: new Tasks are UI-bound or stored/cancelled, and no DispatchQueue/Combine/completion-handler APIs were added.
Cmux Swift @Concurrent ✅ Passed No changed async function is nonisolated without @concurrent; the new create/submit paths stay on @MainActor and are UI-bound.
Cmux Swift File And Package Boundaries ✅ Passed Core task-composer logic lives in package targets; the only app-target change is a focused working-directory helper, and the 475-line sheet stays a cohesive UI screen.
Cmux Swiftpm Lockfiles ✅ Passed PASS: The Package.swift edit is resources-only, the pbxproj diff has no SwiftPM package-reference changes, and no .gitignore or Package.resolved files changed.
Cmux Swift Logging ✅ Passed No added or materially changed print/debugPrint/dump/NSLog/Logger in the modified Swift files; the diff is UI strings/tests only.
Cmux User-Facing Error Privacy ✅ Passed The new user-facing error/toast copy stays generic (“That Mac…”) and doesn’t expose vendor names, env vars, or raw upstream details.
Cmux Full Internationalization ✅ Passed New UI text uses L10n/String(localized), and every added catalog key has en/ja translations.
Cmux Architecture Rethink ✅ Passed Touched Swift code uses explicit snapshot/store ownership and a single submit path; no new sleeps, polling, locks, observers, or duplicate lifecycle ownership in the diff.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed Touched files only add SwiftUI sheets/buttons; no NSWindow/NSPanel/WindowGroup/NSWindowController or cmuxAuxiliaryWindowIdentifiers changes.
Cmux No Test Or Debug Seam In Production Source ✅ Passed Changed prod files add normal internal helpers; no new #if DEBUG/test-only APIs or ForTesting seams, and widened helpers are used by production call sites.
Title check ✅ Passed The title clearly matches the main change: adding an iOS task composer to launch agent workspaces.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat-ios-task-composer

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.

@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: 5

🤖 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.swift`:
- Around line 717-719: The create-workspace in-flight state is split across
three separately mutated optionals, which makes it easy for
`createWorkspaceTask`, `createWorkspaceTaskGroupID`, and
`createWorkspaceTaskSpec` to drift out of sync. Refactor the related state in
`MobileShellComposite` into a single model (for example, an `enum` or `struct`
that represents idle vs in-flight) and update the call sites in
`createWorkspaceRequest`, `createWorkspace`, `cancelRemoteOperationTasks`, and
`clearCreateWorkspaceTask` to read/write that one source of truth. Ensure the
new type carries the task, group ID, and spec together so partial updates are no
longer representable.

In
`@Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileTaskTemplateStore.swift`:
- Around line 31-38: The UserDefaultsMobileTaskTemplateStore is annotated with
`@Observable` but has no tracked mutable state, so SwiftUI won’t observe CRUD
changes. Update the store to either expose and mutate a private(set) templates
property from addTemplate, updateTemplate, and deleteTemplate, or remove
`@Observable` if listTemplates/refreshTemplates remains the intended refresh path.
Keep the fix centered on UserDefaultsMobileTaskTemplateStore and its CRUD
methods so the observation behavior matches the implementation.

In
`@Packages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobileTaskCommandComposer.swift`:
- Around line 32-34: The shellQuoted helper on MobileTaskCommandComposer is
exposed too broadly as public even though it is only an internal implementation
detail. Change shellQuoted to internal so it remains available to `@testable`
imports and tests, but does not widen the public API surface of
CmuxMobileShellModel.

In
`@Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskTemplateIconPicker.swift`:
- Around line 5-9: The picker’s local state is not initialized from an existing
custom-emoji selection, so reopening it can show a blank Custom emoji field and
no highlighted symbol even when TaskTemplateIconPicker.selection already
contains a custom emoji. Update TaskTemplateIconPicker so emojiInput is seeded
from the incoming selection when the view appears or when selection changes, and
make sure the current selection is still reflected in the grid/highlight logic
even when it is not one of the static symbols.
- Around line 64-74: The file-scope `taskTemplateIcon` helper is being used as a
cross-file API, so it should no longer live as a free function. Move this
rendering logic behind `MacAvatarIcon` (for example as a view-producing property
or method on the existing enum/type) and update `TaskComposerSheet.swift` and
`TaskTemplateEditorView.swift` to call through
`MacAvatarIcon.resolve(custom:defaultSymbol:)` instead. Keep the accessibility
and symbol/emoji rendering behavior the same, but make the API owned by
`MacAvatarIcon` instead of the top-level `taskTemplateIcon` function.
🪄 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: a8855535-d7e5-402f-8deb-0a5323a6aaeb

📥 Commits

Reviewing files that changed from the base of the PR and between df264cb and 95b79c2.

📒 Files selected for processing (21)
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+TaskComposer.swift
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+WorkspaceCreateRequest.swift
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite.swift
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileTaskTemplateStore.swift
  • Packages/iOS/CmuxMobileShell/Tests/CmuxMobileShellTests/ComposerSubmitRoutingTestSupport.swift
  • Packages/iOS/CmuxMobileShell/Tests/CmuxMobileShellTests/MobileTaskComposerSubmitTests.swift
  • Packages/iOS/CmuxMobileShell/Tests/CmuxMobileShellTests/MobileTaskTemplateStoreTests.swift
  • Packages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobileTaskCommandComposer.swift
  • Packages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobileTaskTemplate.swift
  • Packages/iOS/CmuxMobileShellModel/Tests/CmuxMobileShellModelTests/MobileTaskCommandComposerTests.swift
  • Packages/iOS/CmuxMobileShellModel/Tests/CmuxMobileShellModelTests/MobileTaskTemplateTests.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerButton.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskTemplateEditorView.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskTemplateIconPicker.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceShellView.swift
  • Sources/TerminalController+WorkspaceCreate.swift
  • cmux.xcodeproj/project.pbxproj
  • cmuxTests/WorkspaceCreateWorkingDirectoryTests.swift
  • ios/cmux/Resources/Localizable.xcstrings
  • ios/cmuxPackage/Sources/cmuxFeature/CMUXMobileRootScene.swift

@greptile-apps

greptile-apps Bot commented Jul 8, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds a floating "New Task" composer to the iOS workspace list, allowing users to create workspaces from agent templates (Claude, Codex, OpenCode, or plain shell) with a selected Mac, working directory, and prompt. Workspace creation is made idempotent via stable operation_id UUIDs, a bounded UserDefaults-backed tombstone cache, and a pinned connection context that prevents stale-Mac results from polluting a new session.

  • Core idempotency: A new WorkspaceCreateIdempotencyCache (capacity 256, persisted to UserDefaults) accepts each operation_id durably before addWorkspace runs; on reconnect the same UUID hits the cache and returns the already-created workspace instead of launching startup commands a second time.
  • Connection pinning: WorkspaceCreatePinnedContext captures Mac device ID, RPC client identity, and connection generation before any suspension; post-response and caught-error dispositions use this snapshot to choose between applying the result, preserving success (legacy path), or failing closed (task-composer path).
  • Draft and session safety: Draft persistence, last-used defaults, and completion are gated on currentSessionGeneration so sign-out clears everything before the next account can restore a previous user's state.
  • Working-directory validation: v2MobileWorkspaceCreate is now async; it runs the directory through a new actor-based WorkspaceCreateWorkingDirectoryValidationService with bounded lanes (1 local, 2 external), per-waiter deadlines, and cancellation propagation before creating the workspace.

Confidence Score: 5/5

This PR is safe to merge. The idempotency, connection-pinning, and session-generation guards are all correctly layered, and the previously flagged issues from earlier rounds have been resolved.

The workspace creation path was thoroughly reworked: idempotency tombstones are written durably before the workspace constructor runs, pinned-context dispositions cover every cancellation/reconnect race, and session-generation checks isolate each account's draft from the next. Actor isolation is maintained throughout — the cache and template store are accessed exclusively from @mainactor contexts. All new user-facing strings have both en and ja translations. The submission state machine correctly prevents double-submission and handles mid-flight dismissal. No correctness bugs were found in the changed paths.

No files require special attention. The most complex files — WorkspaceCreatePinnedContext, WorkspaceCreateIdempotencyCache, and MobileShellComposite+WorkspaceCreateRequest — all have corresponding tests that exercise their critical paths.

Important Files Changed

Filename Overview
Sources/TerminalController+WorkspaceCreate.swift Refactored into preparation/perform split with idempotency cache; sanitized env key validation; v2ExpandedWorkingDirectory tilde expansion; workspaceCreateResult helper. Clean.
Sources/TerminalController+WorkspaceCreateIdempotencyCache.swift New bounded tombstone cache (capacity 256, persisted to UserDefaults). accept() is durable before addWorkspace; associate() links operationID to the live workspace ID. LRU eviction is correct.
Sources/TerminalController+WorkspaceCreatePreparation.swift Parses operation_id, resolves tabManager, checks idempotency cache, and returns a typed outcome (failure/existing/completed/ready). Logic is correct; resolveTaskCreateWorkspace avoids duplicate TabManager traversals.
Sources/TerminalController+WorkspaceCreateWorkingDirectoryValidationService.swift New actor-based coalescing service with local/external lane capacity limits, per-waiter deadlines, and cooperative cancellation. Bounded by maximumPendingWaiters=64. Concurrency model is sound.
Sources/TerminalController+WorkspaceCreateWorkingDirectory.swift O_NOFOLLOW/AT_SYMLINK_NOFOLLOW path walk with mount-locality classification. FD lifecycle is correct (defer+close on each hop). laneClassifier/probe injection enables full unit testing.
Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+WorkspaceCreateRequest.swift createRemoteWorkspace now uses WorkspaceCreatePinnedContext for post-response/caught-error disposition routing. isCurrentWorkspaceCreateContext guard is correctly placed before applyRemoteWorkspaceList.
Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+TaskComposer.swift submitTaskComposer handles reconnect-then-create with a capability guard; session-generation-gated draft/completion helpers are correct.
Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/WorkspaceCreatePinnedContext.swift Captures Mac device ID, client identity, and connection generation before any suspension. postResponseDisposition and caughtErrorDisposition correctly distinguish legacy (preserveSuccess) from idempotent (failClosed) paths.
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet.swift 470-line SwiftUI sheet with draft restoration, submission phase enum, idempotency key rotation on request changes, and per-field disabled guards. Double-tap protection is correct.
Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileTaskTemplateStore.swift UserDefaults-backed @mainactor store with v4 key prefix that clears v1-v3 legacy data on first read. Encoder/decoder are @MainActor-confined.
Packages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobileTaskSubmissionSnapshot.swift Immutable capture of template/prompt/Mac/directory before first suspension. isRequestEquivalent is correct; env dict is always at most one entry in practice.
Sources/TabManager+SessionRestorePolicy.swift Extracted session restore helpers and new recordRestoredTaskCreateProvenance to seed the idempotency cache from restored snapshots. All callers are @mainactor.
ios/cmux/Resources/Localizable.xcstrings Adds ~50 new string keys for the task composer UI, all with both en and ja translations. Full i18n coverage confirmed.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant UI as TaskComposerSheet (iOS)
    participant Store as MobileShellComposite
    participant RPC as MobileCoreRPCSession
    participant Mac as TerminalController (Mac)
    participant Cache as WorkspaceCreateIdempotencyCache

    UI->>Store: submitTaskComposer(macDeviceID, spec, willStartCreate)
    Store->>Store: switchToMac(macDeviceID) if needed
    Store->>Store: captureWorkspaceCreateContext() → pinnedContext
    Store->>Store: createWorkspaceRequest(spec, pinnedContext)
    Store->>Store: "willStartCreate() → submissionPhase = .committed"
    Store->>RPC: sendRequest(workspace.create, params+operation_id)
    RPC->>Mac: workspace.create RPC
    Mac->>Cache: containsCompletedOperation(operationID)?
    alt Already completed
        Cache-->>Mac: true → return workspace list (idempotent)
    else First time
        Mac->>Mac: v2ValidateMobileWorkingDirectory(path)
        Mac->>Cache: accept(operationID) [durable]
        Mac->>Mac: addWorkspace(title, command, env, dir)
        Mac->>Cache: associate(operationID, workspaceID)
        Mac-->>RPC: workspace list + createdWorkspaceID
    end
    RPC-->>Store: Result
    Store->>Store: postResponseDisposition → .apply / .failClosed / .preserveSuccess
    alt .apply (current context, not cancelled)
        Store->>Store: applyRemoteWorkspaceList + setSelectedWorkspaceID
        Store-->>UI: .success(())
        UI->>Store: completeTaskComposerSubmission(snapshot)
        UI->>UI: dismiss()
    else .failClosed (cancelled or stale connection)
        Store-->>UI: .failure(.notConnected)
        UI->>Store: persistTaskComposerDraft(snapshot.draft)
        UI->>UI: show error, keep draft
    end
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 TaskComposerSheet (iOS)
    participant Store as MobileShellComposite
    participant RPC as MobileCoreRPCSession
    participant Mac as TerminalController (Mac)
    participant Cache as WorkspaceCreateIdempotencyCache

    UI->>Store: submitTaskComposer(macDeviceID, spec, willStartCreate)
    Store->>Store: switchToMac(macDeviceID) if needed
    Store->>Store: captureWorkspaceCreateContext() → pinnedContext
    Store->>Store: createWorkspaceRequest(spec, pinnedContext)
    Store->>Store: "willStartCreate() → submissionPhase = .committed"
    Store->>RPC: sendRequest(workspace.create, params+operation_id)
    RPC->>Mac: workspace.create RPC
    Mac->>Cache: containsCompletedOperation(operationID)?
    alt Already completed
        Cache-->>Mac: true → return workspace list (idempotent)
    else First time
        Mac->>Mac: v2ValidateMobileWorkingDirectory(path)
        Mac->>Cache: accept(operationID) [durable]
        Mac->>Mac: addWorkspace(title, command, env, dir)
        Mac->>Cache: associate(operationID, workspaceID)
        Mac-->>RPC: workspace list + createdWorkspaceID
    end
    RPC-->>Store: Result
    Store->>Store: postResponseDisposition → .apply / .failClosed / .preserveSuccess
    alt .apply (current context, not cancelled)
        Store->>Store: applyRemoteWorkspaceList + setSelectedWorkspaceID
        Store-->>UI: .success(())
        UI->>Store: completeTaskComposerSubmission(snapshot)
        UI->>UI: dismiss()
    else .failClosed (cancelled or stale connection)
        Store-->>UI: .failure(.notConnected)
        UI->>Store: persistTaskComposerDraft(snapshot.draft)
        UI->>UI: show error, keep draft
    end
Loading

Reviews (35): Last reviewed commit: "Cancel owned Mac switch with composer ta..." | Re-trigger Greptile

…e selection, test-support builders, and task composer types into own files

- Move route-selection statics out of MobileShellComposite.swift into
  MobileShellComposite+RouteSelection.swift and ratchet the budget entry
  down to 7669 (the +9 task-composer lines are stored properties that
  cannot live in an extension).
- Split ComposerSubmitRoutingTestSupport.swift (509 > 500 threshold)
  into support fixtures + ComposerSubmitRoutingStoreBuilders.swift.
- One major type per file: MobileWorkspaceCreateSpec,
  MobileTaskTemplateStoring, MobileTaskComposition, TaskTemplateFormView,
  TaskTemplateIcon each get their own file.

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet.swift (2)

266-287: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Directory is not recalculated when template selection changes via the editor.

refreshTemplates() updates selectedTemplateID when the current template is deleted, but never recalculates directory. Similarly, addTemplate() selects a new template without resetting didEditDirectory or updating the directory. If the user deletes the selected template (falling back to a different one) or adds a new template with a different default directory, the directory field still shows the old value. This can lead to workspaces created with an unexpected working directory.

🐛 Proposed fix: recalculate directory in refreshTemplates and reset didEditDirectory in addTemplate
 private func addTemplate(_ template: MobileTaskTemplate) {
     store.taskTemplateStore?.addTemplate(template)
     selectedTemplateID = template.id
+    didEditDirectory = false
 }

 private func refreshTemplates() {
     templates = store.taskTemplateStore?.listTemplates() ?? []
     if let selectedTemplateID, templates.contains(where: { $0.id == selectedTemplateID }) {
-        return
+        if !didEditDirectory {
+            directory = Self.suggestedDirectory(
+                template: selectedTemplate,
+                macDeviceID: selectedMacDeviceID,
+                templateStore: store.taskTemplateStore
+            )
+        }
+        return
     }
     selectedTemplateID = templates.first?.id
+    if !didEditDirectory {
+        directory = Self.suggestedDirectory(
+            template: selectedTemplate,
+            macDeviceID: selectedMacDeviceID,
+            templateStore: store.taskTemplateStore
+        )
+    }
 }
🤖 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/TaskComposer/TaskComposerSheet.swift`
around lines 266 - 287, The template selection flow in addTemplate() and
refreshTemplates() leaves directory stale when the selected template changes.
Update addTemplate(_:), refreshTemplates(), and the directory-related state so
that selecting a newly added template resets didEditDirectory and recalculates
directory from the current template, and when refreshTemplates() falls back to a
different selectedTemplateID it also recomputes directory before returning.

81-82: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Store and cancel the submission Task instead of fire-and-forget.

The Task { await submit() } in the Create button action has meaningful lifecycle (network call + state mutations) but is not stored or cancelled. If the user dismisses the sheet mid-submission, the task continues orphaned. As per coding guidelines, fire-and-forget Tasks with real lifecycle should be stored and tied to a caller-owned operation.

♻️ Proposed fix: store the Task and cancel on disappear
+ `@State` private var submitTask: Task<Void, Never>?

 // In the button action:
- Task { await submit() }
+ submitTask = Task { await submit() }

 // Add cancellation on disappear:
+ .onDisappear {
+     submitTask?.cancel()
+ }
🤖 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/TaskComposer/TaskComposerSheet.swift`
around lines 81 - 82, The Create button in TaskComposerSheet currently launches
submit() with a fire-and-forget Task, which leaves the submission running
without ownership. Store the created Task in a sheet-owned property in
TaskComposerSheet, reuse it for the button action instead of creating an
untracked task, and cancel it when the sheet disappears or is dismissed. Make
the lifecycle explicit around the submit() path so the task is tied to the
view’s lifetime.

Source: Coding guidelines

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

5-9: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

emojiInput is still not initialized from the incoming selection.

This was flagged in a previous review: when the picker opens with a custom emoji already in selection, the emoji TextField is blank and no grid cell is highlighted. The current code still initializes emojiInput = "" without seeding from selection.

🐛 Proposed fix: seed emojiInput from the incoming selection
 struct TaskTemplateIconPicker: View {
     `@Binding` var selection: String
-    `@State` private var emojiInput = ""
+    `@State` private var emojiInput: String
+
+    init(selection: Binding<String>) {
+        self._selection = selection
+        let current = selection.wrappedValue
+        self._emojiInput = State(initialValue: Self.symbols.contains(current) ? "" : current)
+    }
🤖 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/TaskComposer/TaskTemplateIconPicker.swift`
around lines 5 - 9, TaskTemplateIconPicker still starts with an empty emojiInput
instead of reflecting the incoming selection, so the TextField and highlighted
symbol state are out of sync when a custom emoji is already selected. Update
TaskTemplateIconPicker’s state setup so emojiInput is seeded from selection when
the view is created, and keep the binding/selection logic in sync in the
initializer or equivalent setup for the picker.
🤖 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.

Outside diff comments:
In
`@Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet.swift`:
- Around line 266-287: The template selection flow in addTemplate() and
refreshTemplates() leaves directory stale when the selected template changes.
Update addTemplate(_:), refreshTemplates(), and the directory-related state so
that selecting a newly added template resets didEditDirectory and recalculates
directory from the current template, and when refreshTemplates() falls back to a
different selectedTemplateID it also recomputes directory before returning.
- Around line 81-82: The Create button in TaskComposerSheet currently launches
submit() with a fire-and-forget Task, which leaves the submission running
without ownership. Store the created Task in a sheet-owned property in
TaskComposerSheet, reuse it for the button action instead of creating an
untracked task, and cancel it when the sheet disappears or is dismissed. Make
the lifecycle explicit around the submit() path so the task is tied to the
view’s lifetime.

---

Duplicate comments:
In
`@Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskTemplateIconPicker.swift`:
- Around line 5-9: TaskTemplateIconPicker still starts with an empty emojiInput
instead of reflecting the incoming selection, so the TextField and highlighted
symbol state are out of sync when a custom emoji is already selected. Update
TaskTemplateIconPicker’s state setup so emojiInput is seeded from selection when
the view is created, and keep the binding/selection logic in sync in the
initializer or equivalent setup for the picker.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 5b4d4ac5-14e2-4374-a829-0f558fbcd8de

📥 Commits

Reviewing files that changed from the base of the PR and between 95b79c2 and d5dd73b.

📒 Files selected for processing (3)
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskTemplateEditorView.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskTemplateIconPicker.swift

- Guard submit() against re-entry and hold the submit Task in state;
  Cancel and drag-to-dismiss now cancel it, and a cancelled submit drops
  its result instead of persisting last-used defaults (Codex P2 +
  Greptile P1).
- Seed the custom-emoji field from an existing emoji selection so
  reopening the icon picker shows the current icon (CodeRabbit).
- Narrow shellQuoted to internal; tests use @testable (CodeRabbit).
- Drop inert @observable from UserDefaultsMobileTaskTemplateStore: it
  has no tracked stored state, views re-read after mutations (CodeRabbit).

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet.swift (2)

108-113: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Disable Cancel button during submission.

The Cancel button is not disabled when isSubmitting is true, so the user can dismiss the sheet while the fire-and-forget Task { await submit() } is still running. The workspace creation continues after dismissal, which may surprise the user.

🔧 Proposed fix
                ToolbarItem(placement: .cancellationAction) {
                    Button(L10n.string("mobile.common.cancel", defaultValue: "Cancel")) {
                        dismiss()
                    }
+                   .disabled(isSubmitting)
                }
🤖 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/TaskComposer/TaskComposerSheet.swift`
around lines 108 - 113, Disable the Cancel action in TaskComposerSheet while
submission is in progress. Update the Button inside the toolbar’s
.cancellationAction to respect isSubmitting so the sheet cannot be dismissed
during the Task { await submit() } flow, and keep the state tied to the existing
submit lifecycle in TaskComposerSheet/submit(). Use the same isSubmitting flag
already used by the form controls to gate the Cancel button’s enabled state.

266-287: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Directory not recalculated after template add or edit.

addTemplate selects a new template but doesn't update directory or reset didEditDirectory, so the directory field won't reflect the new template's defaultDirectory. Similarly, refreshTemplates reloads templates after the editor is dismissed but never recalculates the directory when !didEditDirectory, so editing the selected template's defaultDirectory in the editor leaves the field stale.

Compare with templateChip (line 207-228), which correctly resets didEditDirectory = false and recalculates the directory on selection. addTemplate should follow the same pattern, and refreshTemplates should recalculate when the user hasn't manually edited.

🔧 Proposed fix
 private func addTemplate(_ template: MobileTaskTemplate) {
     store.taskTemplateStore?.addTemplate(template)
     selectedTemplateID = template.id
+    failureText = nil
+    didEditDirectory = false
+    directory = Self.suggestedDirectory(
+        template: template,
+        macDeviceID: selectedMacDeviceID,
+        templateStore: store.taskTemplateStore
+    )
 }

 private func updateTemplate(_ template: MobileTaskTemplate) {
     store.taskTemplateStore?.updateTemplate(template)
 }

 private func deleteTemplates(_ offsets: IndexSet) {
     for index in offsets {
         store.taskTemplateStore?.deleteTemplate(id: templates[index].id)
     }
 }

 private func refreshTemplates() {
     templates = store.taskTemplateStore?.listTemplates() ?? []
     if let selectedTemplateID, templates.contains(where: { $0.id == selectedTemplateID }) {
+        if !didEditDirectory {
+            directory = Self.suggestedDirectory(
+                template: selectedTemplate,
+                macDeviceID: selectedMacDeviceID,
+                templateStore: store.taskTemplateStore
+            )
+        }
         return
     }
     selectedTemplateID = templates.first?.id
+    if !didEditDirectory {
+        directory = Self.suggestedDirectory(
+            template: selectedTemplate,
+            macDeviceID: selectedMacDeviceID,
+            templateStore: store.taskTemplateStore
+        )
+    }
 }
🤖 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/TaskComposer/TaskComposerSheet.swift`
around lines 266 - 287, `TaskComposerSheet` is not keeping the directory state
in sync after template changes. Update `addTemplate(_:)` so it also resets
`didEditDirectory` and recalculates `directory` from the newly added template’s
`defaultDirectory`, mirroring the selection flow used by `templateChip`. Also
update `refreshTemplates()` to recompute `directory` from the currently selected
template whenever `didEditDirectory` is false, so edits to a template’s
`defaultDirectory` are reflected after the editor closes.
♻️ Duplicate comments (1)
Packages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobileTaskCommandComposer.swift (1)

11-13: 📐 Maintainability & Code Quality | 🟠 Major | 💤 Low value

shellQuoted remains public — still should be internal.

This was previously flagged: tests use @testable import CmuxMobileShellModel, so internal access is sufficient. Exposing it as public unnecessarily widens the package's API surface.

♻️ Proposed fix
-    public func shellQuoted(_ value: String) -> String {
+    func shellQuoted(_ value: String) -> String {
🤖 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/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobileTaskCommandComposer.swift`
around lines 11 - 13, The shellQuoted helper in MobileTaskCommandComposer is
still exposing more API surface than needed; change its access level from public
to internal since the test target already uses `@testable` import and can still
access it. Update the declaration of shellQuoted in MobileTaskCommandComposer to
keep it package-internal while preserving its current quoting behavior.
🤖 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`+RouteSelection.swift:
- Around line 13-19: Mark the pure route-selection helpers on
MobileShellComposite as nonisolated static so they do not inherit `@MainActor`
isolation; update prefersNonLoopbackRoutes and the related static helpers
firstReconnectHostPortRoute and isIPLiteralHost in
MobileShellComposite+RouteSelection to remain value-only helpers without
unnecessary actor hops.

---

Outside diff comments:
In
`@Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet.swift`:
- Around line 108-113: Disable the Cancel action in TaskComposerSheet while
submission is in progress. Update the Button inside the toolbar’s
.cancellationAction to respect isSubmitting so the sheet cannot be dismissed
during the Task { await submit() } flow, and keep the state tied to the existing
submit lifecycle in TaskComposerSheet/submit(). Use the same isSubmitting flag
already used by the form controls to gate the Cancel button’s enabled state.
- Around line 266-287: `TaskComposerSheet` is not keeping the directory state in
sync after template changes. Update `addTemplate(_:)` so it also resets
`didEditDirectory` and recalculates `directory` from the newly added template’s
`defaultDirectory`, mirroring the selection flow used by `templateChip`. Also
update `refreshTemplates()` to recompute `directory` from the currently selected
template whenever `didEditDirectory` is false, so edits to a template’s
`defaultDirectory` are reflected after the editor closes.

---

Duplicate comments:
In
`@Packages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobileTaskCommandComposer.swift`:
- Around line 11-13: The shellQuoted helper in MobileTaskCommandComposer is
still exposing more API surface than needed; change its access level from public
to internal since the test target already uses `@testable` import and can still
access it. Update the declaration of shellQuoted in MobileTaskCommandComposer to
keep it package-internal while preserving its current quoting behavior.
🪄 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: 3e5dc5f6-eeac-4ed7-82c4-aed1314bf735

📥 Commits

Reviewing files that changed from the base of the PR and between d5dd73b and 2c9418b.

⛔ Files ignored due to path filters (1)
  • .github/swift-file-length-budget.tsv is excluded by !**/*.tsv
📒 Files selected for processing (15)
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+RouteSelection.swift
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+WorkspaceCreateRequest.swift
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite.swift
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileTaskTemplateStore.swift
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileTaskTemplateStoring.swift
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileWorkspaceCreateSpec.swift
  • Packages/iOS/CmuxMobileShell/Tests/CmuxMobileShellTests/ComposerSubmitRoutingStoreBuilders.swift
  • Packages/iOS/CmuxMobileShell/Tests/CmuxMobileShellTests/ComposerSubmitRoutingTestSupport.swift
  • Packages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobileTaskCommandComposer.swift
  • Packages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobileTaskComposition.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskTemplateEditorView.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskTemplateFormView.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskTemplateIcon.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskTemplateIconPicker.swift
💤 Files with no reviewable changes (5)
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskTemplateIconPicker.swift
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileTaskTemplateStore.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskTemplateEditorView.swift
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite.swift
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+WorkspaceCreateRequest.swift

Seed names are user-facing and persisted; a Japanese fresh install kept
the English 'Shell' label. MobileTaskTemplate.seedDefaults now takes the
display names, the UserDefaults store passes L10n-resolved values, and
the catalog gains mobile.taskComposer.template.seed.* (en/ja).
…with template changes

- A sent workspace.create cannot be recalled, so Cancel is disabled and
  interactive dismissal is blocked while a submit is in flight; the
  bounded RPC always reports success/failure in the sheet instead of a
  cancelled sheet silently creating a remote workspace (Codex P1).
- Directory field now recomputes through one syncSuggestedDirectory()
  path when templates are added, deleted, or edited, not just on chip
  taps, so Create cannot send a deleted/previous template's path
  (Cursor Bugbot). Chip taps and Mac switches share the same helper.

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskTemplateIconPicker.swift (1)

56-69: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Tapping a symbol leaves the custom-emoji field showing a stale value.

The new init (lines 9-15) can now seed emojiInput with a non-empty custom emoji. If the user then taps a symbol in iconButton, selection updates but emojiInput is never cleared, so the text field still displays the old emoji even though a symbol is now selected — a newly-reachable inconsistent UI state introduced by the init fix (previously emojiInput always started empty, so this couldn't happen).

🐛 Proposed fix: clear the field when a symbol is chosen
     private func iconButton(value: String) -> some View {
         let isSelected = selection == value
         Button {
             selection = value
+            if !emojiInput.isEmpty {
+                emojiInput = ""
+            }
         } label: {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskTemplateIconPicker.swift`
around lines 56 - 69, In TaskTemplateIconPicker, selecting a symbol via
iconButton currently updates selection but leaves emojiInput unchanged, which
can keep the custom-emoji field stale after the new init seeds it with a value.
Update the Button action inside iconButton(value:) so it also clears emojiInput
when a symbol is chosen, keeping the selection state and text field in sync.
🤖 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.

Outside diff comments:
In
`@Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskTemplateIconPicker.swift`:
- Around line 56-69: In TaskTemplateIconPicker, selecting a symbol via
iconButton currently updates selection but leaves emojiInput unchanged, which
can keep the custom-emoji field stale after the new init seeds it with a value.
Update the Button action inside iconButton(value:) so it also clears emojiInput
when a symbol is chosen, keeping the selection state and text field in sync.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 14831cc8-c19e-40b7-a74d-b6653ec58c74

📥 Commits

Reviewing files that changed from the base of the PR and between 2c9418b and 7aa4b90.

📒 Files selected for processing (7)
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileTaskTemplateStore.swift
  • Packages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobileTaskCommandComposer.swift
  • Packages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobileTaskTemplate.swift
  • Packages/iOS/CmuxMobileShellModel/Tests/CmuxMobileShellModelTests/MobileTaskTemplateTests.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskTemplateIconPicker.swift
  • ios/cmux/Resources/Localizable.xcstrings

Restored lastMacDeviceID is now validated against displayPairedMacs
before use, mirroring the template-id validation, so a forgotten Mac
cannot become the default target (Cursor Bugbot).
Dogfood feedback: the floating button sat too high (bottom padding 20 -> 6)
and the seeded templates used generic SF Symbols. Claude/Codex/OpenCode now
render their brand images (AgentIcons imagesets copied from the macOS
catalog into CmuxMobileShellUI resources, agent: icon values), OpenCode is
seeded as a fourth default, and the icon picker offers the brand icons ahead
of the symbol grid. Template storage keys bump to v2 (feature is unshipped;
dogfood-only v1 data is dropped and cleaned up).
Namespaced (AgentIcons/...) image lookups fail at runtime inside the
SwiftPM resource bundle: CoreUI reports 'No image named ... found in
asset catalog' even though assetutil shows the namespaced entries in the
compiled Assets.car. Root-level names resolve.
Dev reloads pass PRODUCT_BUNDLE_IDENTIFIER as a global xcodebuild
override, which stamps every SwiftPM resource bundle with the app's own
identifier. CoreUI registers asset catalogs per bundle identifier, so
the package catalog loses to the app catalog and Image(named:) fails
for every entry (namespaced or flat). Ship the PNGs as .copy resources
and load @3x files by explicit URL with a dark-variant pick for Codex.
A workspace.create that throws after the connection generation changed
mid-flight is mapped to .success, so the composer dismisses and persists
last-used defaults for a task that was never created (Cursor bot finding
on PR 7670). Red commit; fix follows.
A workspace.create that throws after the connection generation changed
mid-flight was mapped to .success, so the composer dismissed and
persisted last-used defaults for a task that was never created. Only a
cancelled request (whose result the sheet drops) still maps to success;
a stale failure now returns its mapped failure while skipping the
connection-state side effects that belong to the new connection.
submitTaskComposer skipped switchToMac whenever the selected Mac id
matched foregroundMacDeviceID, but a dropped connection leaves that id
in place with remoteClient nil, so submit failed as not-connected
without attempting a reconnect. Also switch when remoteClient is nil;
switchToMac already short-circuits when the foreground connection is
genuinely live.
@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.

@azooz2003-bit azooz2003-bit changed the title iOS: floating task composer creates workspaces with agent templates iOS: launch agent workspaces from the task composer Jul 17, 2026
cmux reload-cloud added 18 commits July 17, 2026 16:57
# Conflicts:
#	Packages/iOS/CmuxMobileRPC/Sources/CmuxMobileRPC/MobileCoreRPCSession.swift
#	Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+WorkspaceCreateRequest.swift
#	Sources/TerminalController.swift
# Conflicts:
#	Packages/iOS/CmuxMobileRPC/Sources/CmuxMobileRPC/MobileCoreRPCSession.swift
# Conflicts:
#	ios/cmuxPackage/Sources/cmuxFeature/CMUXMobileRootScene.swift
@azooz2003-bit
azooz2003-bit merged commit f937305 into main Jul 20, 2026
5 checks passed
@azooz2003-bit
azooz2003-bit deleted the feat-ios-task-composer branch July 20, 2026 21:34
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.

2 participants