Skip to content

Show iOS terminal send progress and failures - #9723

Merged
azooz2003-bit merged 7 commits into
mainfrom
feat-ios-send-status
Aug 7, 2026
Merged

azooz2003-bit merged 7 commits into
mainfrom
feat-ios-send-status

Conversation

@azooz2003-bit

@azooz2003-bit azooz2003-bit commented Aug 6, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

  • show an in-progress spinner while iOS prompt and terminal-command sends await the Mac
  • clear the spinner back to the normal Send control after acknowledgement, with no success checkmark or Sent state
  • show an explicit failure state, preserve failed drafts, and bind settlement to the newest queued send

Verification

The selected hosted iOS workflow could not execute its UI test because unrelated existing package tests and a repository-wide pairing lint fail to compile or pass on this branch. The focused send-status tests pass, and the device archive compiles successfully.


Note

Medium Risk
Changes the terminal input send pipeline and composer UX across store, buffer, and RPC settlement paths, but behavior is scoped to user-visible status and preserves existing retry/staging semantics with added race guards.

Overview
Adds per-terminal send settlement for iOS composer submits and Return-terminated raw commands, so users see in-flight progress and explicit failures instead of silent hangs or lost drafts.

The shell store tracks MobileTerminalSendStatus (sending / sent / failed) with operation IDs so a late RPC from an older send cannot overwrite a newer retry. Composer submitComposer wraps the full image+text run; raw input only enters this path when the buffered text contains a line break or carriage return. Non-submit keystrokes do not flash chrome; editing the draft clears a settled failure state. Focus/connection teardown fails pending raw sends without disturbing an in-flight composer paste on the same terminal.

UI: The composer Send button shows a spinner while sending, disables double-tap, surfaces a failure caption and red styling on error, and returns to the normal Send affordance after success (no persistent “sent” checkmark). When the composer is hidden, a status pill overlays the terminal surface for sending/failed.

The raw input send buffer carries the newest operation ID through coalesced chunks. Tests cover routing, queued Return races, draft restore after failure, and UI tests hold/release terminal.paste on the mock host.

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

Summary by CodeRabbit

  • New Features

    • Added send-status feedback for terminal submissions: Sending, Sent, and Failed.
    • Send controls are disabled while a submission is in progress.
    • Failed submissions show an inline error and preserve the draft for retry.
    • Status indicators appear in the composer and terminal workspace views.
    • Added English and Japanese localized status messages.
  • Bug Fixes

    • Improved handling of queued, rejected, delayed, and stale terminal submissions to prevent incorrect status updates.
  • Tests

    • Added coverage for submission status transitions, failure recovery, draft restoration, and queued input ordering.

@coderabbitai

coderabbitai Bot commented Aug 6, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Terminal composer and Return-key submissions now track per-terminal sending, success, and failure states. The iOS interface displays these states, preserves failed drafts, and validates delayed and rejected submissions.

Changes

Terminal send status

Layer / File(s) Summary
Status contract and composer submission
Packages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobileTerminalSendStatus.swift, Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite.swift
Adds public send states and UUID-based tracking for composer submissions. Composer submission returns success or failure and preserves content after image-send failure.
Raw input status settlement
Packages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobileTerminalInputSendBuffer.swift, Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite.swift
Tracks Return-terminated input through coalescing, queue rejection, connection changes, pipeline errors, RPC success, and RPC failure.
iOS status rendering
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/*, ios/cmux/Resources/Localizable.xcstrings
Updates the composer send button and adds inline and workspace status indicators with English and Japanese localization.
Status transition validation
Packages/iOS/CmuxMobileShell/Tests/CmuxMobileShellTests/*, ios/cmuxUITests/cmuxUITests.swift
Tests sending, sent, and failed states for composer and raw input submissions. UI tests cover delayed rejection, disabled sending, and draft preservation.

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

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant TerminalComposerView
  participant MobileShellComposite
  participant TerminalRPC
  User->>TerminalComposerView: Submit terminal content
  TerminalComposerView->>MobileShellComposite: submitComposer()
  MobileShellComposite->>TerminalComposerView: Set status to sending
  MobileShellComposite->>TerminalRPC: Send terminal paste or input
  TerminalRPC-->>MobileShellComposite: Return success or failure
  MobileShellComposite->>TerminalComposerView: Set status to sent or failed
Loading

Possibly related PRs

Suggested reviewers: lawrencecchen


Important

Pre-merge checks failed

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

❌ Failed checks (1 error, 1 warning)

Check name Status Explanation Resolution
Cmux Architecture Rethink ❌ Error finishTerminalSend stores .sent indefinitely; clearSettledTerminalSendStatus runs only on later edits, so the model exposes stale success state despite the stated sending -> idle invariant. Make MobileShellComposite the single owner with success settling directly to .idle; remove .sent and update tests/UI to assert idle after acknowledgement.
Description check ⚠️ Warning The description explains the change and verification, but omits the required Demo Video, Review Trigger, and Checklist sections. Add the required Demo Video, Review Trigger, and Checklist sections, and rename or expand Verification to cover the template's Testing section.
✅ Passed checks (23 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 New mutable send state remains in @MainActor MobileShellComposite; ordered settlement callbacks are @MainActor, model additions are value Sendable types, and SwiftUI changes are allowed UI types.
Cmux Swift Blocking Runtime ✅ Passed PR-range diff adds no prohibited blocking or timing primitives to production Swift; polling/XCTWaiter changes are test-only, and production status settles through existing async RPC callbacks.
Cmux Browser Automation Off-Main ✅ Passed The PR range changes only iOS send-status files and tests; no browser automation, WebKit/AppKit routing, or socket-worker policy files changed.
Cmux Expensive Synchronous Load ✅ Passed The PR adds send-status state, RPC settlement, and SwiftUI status rendering only. No changed production line adds RestorableAgentSessionIndex, file/history loads, broad scans, or synchronous JSON/J...
Cmux Cache Substitution Correctness ✅ Passed The PR adds in-memory terminal send-status state and transient UI only; its diff does not replace any fresh persistence, history, undo, or snapshot read with a cached value.
Cmux No Hacky Sleeps ✅ Passed The aggregate PR diff changes only 11 Swift files and one localization file; it changes no TypeScript, JavaScript, shell, or build/runtime script covered by this check.
Cmux Algorithmic Complexity ✅ Passed The production diff adds O(1) status dictionary access, one linear Unicode-scalar scan, and one linear pending-operation pass; no nested scalable scans or repeated sorting/filtering were introduced.
Cmux Swift Concurrency ✅ Passed The changed cmux-owned production Swift adds no Dispatch queues, Combine state, completion-handler APIs, or fire-and-forget Tasks; added async constructs are test synchronization or an NWConnection...
Cmux Swift @Concurrent ✅ Passed Changed async methods remain on @MainActor; the only new nonisolated helper is synchronous and pure, and existing network paths retain their explicit MainActor task boundary.
Cmux Swift Package Boundaries ✅ Passed Production Swift changes stay in existing SwiftPM targets CmuxMobileShell, CmuxMobileShellModel, and CmuxMobileShellUI; reusable status and buffer types are already in the model package.
Cmux Swiftpm Lockfiles ✅ Passed The PR diff changes no Package.swift, Package.resolved, .gitignore, workflow, or Xcode project package-reference files; the lockfile rule is not triggered.
Cmux Swift Logging ✅ Passed The full PR diff adds no print, debugPrint, dump, NSLog, ad hoc output, or logger calls; the existing NSLog and Logger declaration remain outside changed hunks.
Cmux User-Facing Error Privacy ✅ Passed Production UI uses only generic copy: “Couldn’t send. Check the connection and try again.”, “Send failed”, and “Sending”; no upstream, internal, credential, token, or raw-error details were added.
Cmux Full Internationalization ✅ Passed Changed Swift UI text uses L10n.string with default values, and ios/cmux/Resources/Localizable.xcstrings contains matching translated en and ja entries for all new keys.
Cmux Swiftui State Layout ✅ Passed Changed SwiftUI code adds no ObservableObject/@published, GeometryReader, lazy-row store reference, or render-time state mutation; send status is plain state on the existing @Observable store.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed The PR changes iOS terminal send-status logic and SwiftUI views, not standalone windows; no window constructs were added, and scripts/lint_auxiliary_window_close_shortcuts.py passes.
Cmux Source Artifacts ✅ Passed The complete PR diff contains only regular 100644 Swift source/tests and one localization catalog; no artifact directories, logs, caches, build output, screenshots, or copied binaries were added.
Cmux No Test Or Debug Seam In Production Source ✅ Passed The production diff adds no test/debug-named seam or DEBUG test guard; terminalSendStatus is consumed by TerminalComposerView and WorkspaceDetailView, and existing ForTesting seams are unchanged.
Cmux No Ambient Global State ✅ Passed The production diff adds send state and behavior to constructable MobileShellComposite instances; other additions are data/UI types, with no new top-level API, mutable global, static namespace, or...
Title check ✅ Passed The title clearly describes the primary change: iOS terminal send progress and failure handling.
✨ 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-ios-send-status

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: 3

🤖 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 `@ios/cmuxUITests/cmuxUITests.swift`:
- Around line 7533-7549: Replace the terminalSendResponseDelay-based
DispatchQueue.asyncAfter flow in ios/cmuxUITests/cmuxUITests.swift:7533-7549
with explicit request/release synchronization: wait until the request reaches
the expected state, assert it is Sending, then signal the release gate before
sending the response. Apply the same synchronization changes to the related
sites at ios/cmuxUITests/cmuxUITests.swift:2791-2813 and 7219-7294, removing
wall-clock timing dependencies while preserving the existing sendResponse
behavior.

In
`@Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite.swift`:
- Around line 7702-7713: The raw input queue must retain each submission UUID
alongside its queued chunk instead of using the mutable per-terminal entry in
prepareTerminalSendStatusForRawInput. Update the queue and
drainRawTerminalInputBuffer so batching preserves the newest submission UUID,
then pass that UUID to sendRemoteTerminalInput; add a regression test covering a
held first request, a queued second Return submission, and rejection of the
second request.

In
`@Packages/iOS/CmuxMobileShell/Tests/CmuxMobileShellTests/TerminalRawInputOrderingTests.swift`:
- Around line 522-535: Update waitForTerminalSendStatus to use a ContinuousClock
deadline rather than a fixed 200-iteration Task.yield loop. Keep
terminalSendStatus(forTerminalID:) == expected as the success condition,
continue yielding between checks, and return false only when the deadline
expires.
🪄 Autofix

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 Plus

Run ID: 12b2a14e-08fa-49dc-995d-691ce33b394c

📥 Commits

Reviewing files that changed from the base of the PR and between 2280f9c and 42b1240.

📒 Files selected for processing (9)
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite.swift
  • Packages/iOS/CmuxMobileShell/Tests/CmuxMobileShellTests/ComposerSubmitRoutingTests.swift
  • Packages/iOS/CmuxMobileShell/Tests/CmuxMobileShellTests/TerminalRawInputOrderingTests.swift
  • Packages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobileTerminalSendStatus.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TerminalComposerView.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TerminalSendStatusPill.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView.swift
  • ios/cmux/Resources/Localizable.xcstrings
  • ios/cmuxUITests/cmuxUITests.swift

Comment thread ios/cmuxUITests/cmuxUITests.swift

@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 `@ios/cmuxUITests/cmuxUITests.swift`:
- Around line 2840-2841: In the test flow after
awaitTerminalPasteRequestReached(), wait for the send XCUIElement’s label to
become “Sending” using an XCTNSPredicateExpectation before asserting it. Keep
the server response blocked until this UI expectation completes, then retain the
equality assertion.
🪄 Autofix

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 Plus

Run ID: 31cc47aa-0f4b-4887-b324-ecbb02408010

📥 Commits

Reviewing files that changed from the base of the PR and between 4c0086d and 46aad06.

📒 Files selected for processing (4)
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TerminalComposerView.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TerminalSendStatusPill.swift
  • ios/cmux/Resources/Localizable.xcstrings
  • ios/cmuxUITests/cmuxUITests.swift
💤 Files with no reviewable changes (1)
  • ios/cmux/Resources/Localizable.xcstrings

Comment thread ios/cmuxUITests/cmuxUITests.swift Outdated

@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 2 potential issues.

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 46aad06. Configure here.

@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/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite.swift (1)

518-523: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Clear settled status when the user deletes the draft.

Line 519 makes status clearing depend on the new text being non-empty. If a user deletes a failed draft in one edit, the didSet below still persists the empty draft, but this branch does not call clearSettledTerminalSendStatus. The model can keep .failed with no draft to retry.

Track whether the mutation is a user edit or programmatic reconciliation at the mutation boundary. Clear settled status for every user edit, including a transition to an empty draft. Add a regression test for this transition.

🤖 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/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite.swift`
around lines 518 - 523, The terminalInputText mutation logic in
MobileShellComposite currently skips clearSettledTerminalSendStatus when a user
deletes the draft; distinguish user edits from programmatic reconciliation at
the mutation boundary, then clear the settled status for every user edit,
including empty-text transitions, while preserving reconciliation behavior. Add
a regression test covering deletion of a failed draft and verifying its status
is cleared.
🤖 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/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite.swift`:
- Around line 518-523: The terminalInputText mutation logic in
MobileShellComposite currently skips clearSettledTerminalSendStatus when a user
deletes the draft; distinguish user edits from programmatic reconciliation at
the mutation boundary, then clear the settled status for every user edit,
including empty-text transitions, while preserving reconciliation behavior. Add
a regression test covering deletion of a failed draft and verifying its status
is cleared.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 267d21f5-3f6f-4330-b9f9-fa624f509786

📥 Commits

Reviewing files that changed from the base of the PR and between 46aad06 and 9a887b9.

📒 Files selected for processing (4)
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite.swift
  • Packages/iOS/CmuxMobileShell/Tests/CmuxMobileShellTests/ComposerSubmitRoutingStoreBuilders.swift
  • Packages/iOS/CmuxMobileShell/Tests/CmuxMobileShellTests/ComposerSubmitRoutingTests.swift
  • ios/cmuxUITests/cmuxUITests.swift

@azooz2003-bit
azooz2003-bit merged commit 9d8d66b into main Aug 7, 2026
8 checks passed
@azooz2003-bit
azooz2003-bit deleted the feat-ios-send-status branch August 7, 2026 04:36
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