Skip to content

Show iOS terminal loading diagnostics - #7198

Closed
lawrencecchen wants to merge 6 commits into
mainfrom
task-show-terminal-loading-state
Closed

lawrencecchen wants to merge 6 commits into
mainfrom
task-show-terminal-loading-state

Conversation

@lawrencecchen

@lawrencecchen lawrencecchen commented Jul 2, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • Replace the blank iOS workspace detail fallback with a loading diagnostics overlay.
  • Surface Mac connection state, terminal metadata state, Tailscale state, route state, and network guidance.
  • Add focused model coverage and localized strings.

Verification

  • git diff --check
  • Localizable.xcstrings JSON parse
  • iOS simulator reload: CMUX_ALLOW_LOCAL_XCODEBUILD=1 ios/scripts/reload.sh --tag ldterm --simulator "iPhone 17"
  • iOS physical reload: ./ios/scripts/reload-cloud.sh --tag ldterm --device-id E4058DA9-F4C7-52DD-951D-0354061B8E89
  • macOS tagged reload: CMUX_PORT=3942 ./scripts/reload-cloud.sh --tag ldterm

Note: focused package tests are blocked locally by the hq guard for xcodebuildmcp test, and direct SwiftPM test resolution hits the existing macOS platform mismatch for this package graph.


View with Codesmith Autofix with Codesmith
Need help on this PR? Tag /codesmith with what you need. Autofix is disabled.


Note

Low Risk
iOS-only UI and presentation logic with unit tests on the model builder; reconnect/refresh paths reuse existing store APIs without changing attach or auth behavior.

Overview
Replaces the blank iOS workspace detail state (Mac connected but no terminal surface yet) with a TerminalLoadingDiagnosticsOverlay that explains what is happening and what to do next.

The overlay builds copy and tone-coded rows (Mac, terminals, Tailscale, route, optional network guidance) via TerminalLoadingDiagnosticsModel / ModelBuilder, reads Tailscale from the environment, and after 10s with zero terminals switches from a spinner to “No terminals yet” with Refresh (resets the timer and calls switchToMac + reconnectOrRefresh) and Create Terminal.

WorkspaceDetailView+TerminalLoadingDiagnostics scopes connection status, active route, and store errors to the workspace’s Mac (foreground Mac, active ticket, or paired aliases); otherwise the workspace Mac is treated as unavailable and TerminalDisconnectedOverlay uses that same status and the workspace Mac display name.

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


Summary by cubic

Adds an iOS terminal loading diagnostics overlay for connected Macs when terminals haven’t loaded yet. After 10s it switches to “No terminals yet” with Refresh/Create actions, and routes/network diagnostics are scoped to the active or paired Mac only.

  • New TerminalLoadingDiagnosticsOverlay and model (with builder): spinner with a 10s timeout that flips to an empty state; tone-dot rows for Mac, Terminals, Tailscale, and Route; optional network guidance; Refresh/Create Terminal buttons with accessibility IDs. Refresh resets the timer, switches to the workspace’s Mac, and calls reconnectOrRefresh.
  • Integrated into WorkspaceDetailView: uses tailscaleStatusMonitor; computes loadingDiagnosticsConnectionStatus and shows the overlay only when it’s Connected; scopes active route and connection errors to the matching foreground/active/paired Mac; falls back to a saved route from paired Mac metadata; shares the status with TerminalDisconnectedOverlay and uses the workspace’s Mac name for the host label.
  • Added unit tests and EN/JA mobile.terminal.loading.* strings.

Written for commit 2858674. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features
    • Added an iOS “Loading terminals” diagnostics overlay to the terminal detail screen, displaying Mac identity, connection/tailnet status, route context, and per-row diagnostics while terminals load.
    • Includes localized UI for route/network states, a Refresh action, and an optional Create Terminal button when allowed.
  • Tests
    • Added iOS unit tests validating diagnostics model titles, messages, and row formatting across loading/error scenarios.

@vercel

vercel Bot commented Jul 2, 2026 •

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
cmux Canceled Canceled Jul 3, 2026 4:27am
cmux-staging Building Building Preview, Comment Jul 3, 2026 4:27am

@coderabbitai

coderabbitai Bot commented Jul 2, 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 an iOS terminal-loading diagnostics overlay, wires it into WorkspaceDetailView, adds tests for the diagnostics model, and adds new mobile.terminal.loading.* localization entries.

Changes

Terminal Loading Diagnostics Overlay

Layer / File(s) Summary
Diagnostics model and snapshot logic
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TerminalLoadingDiagnosticsRow.swift, Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TerminalLoadingDiagnosticsTone.swift, Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TerminalLoadingDiagnosticsModel.swift, Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TerminalLoadingDiagnosticsModelBuilder.swift
Defines the diagnostics tone and row types, the model wrapper, and the builder that formats mac, terminal, tailnet, route, and network warning rows with localized text and tone mapping.
Overlay rendering and timeout
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TerminalLoadingDiagnosticsOverlay.swift
Implements the SwiftUI overlay that renders loading progress, headline text, diagnostics rows, tone-colored indicators, the optional create-terminal action, and timeout-driven refresh behavior.
Loading diagnostics helpers
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView+TerminalLoadingDiagnostics.swift
Adds the loading-diagnostics connection and route helpers, foreground matching, error exposure, and refresh support used by the overlay.
WorkspaceDetailView integration
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView.swift
Adds the new import and environment dependency, and inserts the diagnostics overlay into the iOS detail view when no terminal is selected.
Diagnostics model tests
Packages/iOS/CmuxMobileShellUI/Tests/CmuxMobileShellUITests/TerminalLoadingDiagnosticsModelTests.swift
Adds iOS tests for the diagnostics model across connection, tailnet, and route states, plus a row lookup helper.
Localization strings for terminal loading
Resources/Localizable.xcstrings, ios/cmux/Resources/Localizable.xcstrings
Adds mobile.terminal.loading.* localization entries in both xcstrings files for create-terminal, mac, route, tailnet, network, and terminal-list text.

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


Important

Pre-merge checks failed

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

❌ Failed checks (3 errors, 1 warning)

Check name Status Explanation Resolution
Cmux Swift Blocking Runtime ❌ Error TerminalLoadingDiagnosticsOverlay adds a new production sleep timeout via ContinuousClock().sleep(for: .seconds(10)). Replace the sleep-based deadline with a real terminal-metadata state signal or cancellable timer/async source owned by the model/actor.
Cmux Algorithmic Complexity ❌ Error loadingDiagnosticsStoredRouteDescription scans displayPairedMacs and calls pairedMacAliasIDs(for:) inside the predicate, creating an O(n²) paired-Mac lookup on a hot UI path. Precompute a direct alias/representative map or cache the stored-route description so the overlay uses one pass, not a nested scan.
Cmux User-Facing Error Privacy ❌ Error The new loading overlay surfaces route labels like “Iroh”/“Tailscale”/“WebSocket” in user-visible recovery copy, which violates the rule on exposing vendor/implementation names. Rename the diagnostics rows to generic user terms (e.g. direct/relay/network) and keep transport/vendor names in internal logs or telemetry only.
Description check ⚠️ Warning It covers Summary and verification, but omits required Demo Video, Review Trigger, and Checklist sections and does not follow the template exactly. Add the missing Demo Video, Review Trigger, and Checklist sections, and rename or map Verification to the template's Testing section.
✅ Passed checks (21 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 The new code is UI-only; CMUXMobileShellStore is @MainActor, and the added views/timeouts follow existing main-actor SwiftUI patterns without new Sendable or protocol isolation debt.
Cmux Browser Automation Off-Main ✅ Passed PASS: The PR only adds iOS terminal-loading UI, model, tests, and strings; it doesn't touch browser socket automation routing files or add any browser.wait/mainActor changes.
Cmux Expensive Synchronous Load ✅ Passed Changed iOS overlay/model files add no sync agent-history loads or JSON/JSONL scans; only async UI tasks and lightweight formatting appear in the diff.
Cmux Cache Substitution Correctness ✅ Passed PASS: Changes only feed a transient iOS loading overlay; no persistence/history/undo/snapshot path substitutes cache for authoritative reads, and refresh still calls the store.
Cmux No Hacky Sleeps ✅ Passed The PR is Swift/iOS-only; no TypeScript/JS/shell/build runtime sleeps were added, and the only delay is a presentation timeout in SwiftUI, which is out of scope here.
Cmux Swift Concurrency ✅ Passed The PR uses Swift concurrency (.task, Task) only at SwiftUI callback/lifecycle boundaries and adds no new DispatchQueue/Combine/completion-handler legacy async patterns.
Cmux Swift @Concurrent ✅ Passed PASS: New async work is UI-bound; .task only sleeps and updates @State, and refresh hops into Task before awaiting store async calls. No @concurrent misuse.
Cmux Swift File And Package Boundaries ✅ Passed The feature lives in the CmuxMobileShellUI SwiftPM package and is split into small coherent files; the only oversized touched file, WorkspaceDetailView, gets just +24 lines incidentally.
Cmux Swiftpm Lockfiles ✅ Passed No Package.swift, .gitignore, workflow, or Package.resolved changes appear in the diff; only source and localization files changed.
Cmux Swift Logging ✅ Passed The PR adds no print/debugPrint/dump/NSLog or Logger changes in runtime code; the only NSLog found is pre-existing and wrapped in #if DEBUG.
Cmux Full Internationalization ✅ Passed New UI text is localized via L10n/String(localized:), and every new mobile.terminal.loading key has en/ja entries in the touched catalog.
Cmux Swiftui State Layout ✅ Passed PASS: The new overlay uses @State and .task, not ObservableObject/@published or GeometryReader; its ForEach is over value rows outside any lazy/list boundary.
Cmux Architecture Rethink ✅ Passed PASS: The new 10s wait is a local UI timeout, not a race workaround; state ownership stays clear in WorkspaceDetailView/model and no duplicate lifecycle owner was added.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed Diff adds only iOS SwiftUI views/models; no NSWindow/WindowGroup/WindowController or cmuxAuxiliaryWindowIdentifiers changes appear.
Cmux Source Artifacts ✅ Passed All changed paths are intentional source/tests/localization files; no logs, temp dirs, build output, or other artifact paths appear in the diff.
Cmux No Test Or Debug Seam In Production Source ✅ Passed Touched Sources files add normal production UI/model code only; no new test-only or DEBUG seam members were introduced, and existing DEBUG code is unchanged.
Cmux No Ambient Global State ✅ Passed No new file-scope funcs, globals, static-only namespaces, or singletons; the added behavior lives on view/model types and extensions.
Title check ✅ Passed The title clearly matches the main change: showing an iOS terminal loading diagnostics overlay.
✨ 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 task-show-terminal-loading-state

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

Copy link
Copy Markdown
Contributor

Greptile Summary

Adds an iOS terminal loading diagnostics overlay to WorkspaceDetailView that replaces the blank fallback when a Mac is connected but no terminal surface has loaded yet. A 10-second ContinuousClock deadline switches the display from a spinner to "No terminals yet" recovery copy with Refresh and Create Terminal actions.

  • New overlay + model pipeline: TerminalLoadingDiagnosticsOverlay computes its TerminalLoadingDiagnosticsModel via a value-type builder, showing tone-coded rows for Mac identity, terminal list, Tailscale state, active/saved route, and an optional network guidance row.
  • WorkspaceDetailView wiring: loadingDiagnosticsConnectionStatus now treats a workspace whose paired Mac does not match the active/foreground ticket as .unavailable, scoping route and error rows to the correct Mac; TerminalDisconnectedOverlay reuses the same status and now shows macDisplayName for the host label.
  • Localization + tests: All new user-facing strings are backed by EN and JA entries in both xcstrings catalogs; unit tests cover the key title/message/tone/row states.

Confidence Score: 5/5

iOS-only UI change with no auth, data model, or persistence changes; connection refresh reuses existing store APIs and all new user-facing strings are fully localized.

The overlay is purely presentational: the 10-second deadline uses structured-concurrency sleep with correct cancellation via .task(id:), SwiftUI state is managed cleanly as value types, the diagnostic Mac-matching logic is scoped to display and does not gate any write path, and all four model states are covered by focused unit tests.

No files require special attention. The WorkspaceDetailView+TerminalLoadingDiagnostics.swift pairedMacAliasIDs branching is the most complex logic path, but it is exercised end-to-end through the existing store APIs.

Important Files Changed

Filename Overview
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TerminalLoadingDiagnosticsModelBuilder.swift Builder struct that assembles the model from connection/route/tailscale state; all user-facing strings go through L10n with format keys in both xcstrings catalogs.
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TerminalLoadingDiagnosticsOverlay.swift SwiftUI overlay using .task(id:) for the 10-second presentation deadline; task cancellation wired correctly via deadlineTaskID; retryLoading increments refreshGeneration to reset the timer.
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView+TerminalLoadingDiagnostics.swift Extension on WorkspaceDetailView that derives loadingDiagnosticsConnectionStatus, scopes route/error state to the matched Mac, and provides refreshLoadingDiagnosticsConnection().
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView.swift Integrates tailscaleStatusMonitor environment value, wires the new overlay into the blank-fallback branch, and updates TerminalDisconnectedOverlay to use loadingDiagnosticsConnectionStatus and macDisplayName.
Packages/iOS/CmuxMobileShellUI/Tests/CmuxMobileShellUITests/TerminalLoadingDiagnosticsModelTests.swift Four focused model tests covering loading/timeout/error/route states; lives in Tests/ and uses @testable import correctly.
Resources/Localizable.xcstrings Adds 20 new mobile.terminal.loading.* keys with EN and JA translations, consistent with the existing catalog locale set.
ios/cmux/Resources/Localizable.xcstrings Mirror of Resources/Localizable.xcstrings additions; identical EN/JA key set for the app-target catalog.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[WorkspaceDetailView body] --> B{Terminal selected?}
    B -- Yes --> C[Render terminal]
    B -- No --> D{loadingDiagnosticsConnectionStatus}
    D -- .connected --> E[TerminalPalette.background + overlay]
    E --> F[TerminalLoadingDiagnosticsOverlay]
    F --> G{.task id=deadlineTaskID}
    G --> H[ContinuousClock.sleep 10s]
    H -- Cancelled --> I[Task restarts on status/count/generation change]
    H -- Completed + still empty --> J[terminalMetadataTimedOut = true]
    F --> K{model.isLoading?}
    K -- true --> L[Spinner + Loading terminals...]
    K -- false --> M[Terminal icon + No terminals yet]
    F --> N[Refresh button]
    N --> O[refreshGeneration += 1]
    O --> G
    N --> P[store.switchToMac + reconnectOrRefresh]
    F --> Q[Create Terminal button]
    D -- .reconnecting / .unavailable --> R[TerminalDisconnectedOverlay]
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"}}}%%
flowchart TD
    A[WorkspaceDetailView body] --> B{Terminal selected?}
    B -- Yes --> C[Render terminal]
    B -- No --> D{loadingDiagnosticsConnectionStatus}
    D -- .connected --> E[TerminalPalette.background + overlay]
    E --> F[TerminalLoadingDiagnosticsOverlay]
    F --> G{.task id=deadlineTaskID}
    G --> H[ContinuousClock.sleep 10s]
    H -- Cancelled --> I[Task restarts on status/count/generation change]
    H -- Completed + still empty --> J[terminalMetadataTimedOut = true]
    F --> K{model.isLoading?}
    K -- true --> L[Spinner + Loading terminals...]
    K -- false --> M[Terminal icon + No terminals yet]
    F --> N[Refresh button]
    N --> O[refreshGeneration += 1]
    O --> G
    N --> P[store.switchToMac + reconnectOrRefresh]
    F --> Q[Create Terminal button]
    D -- .reconnecting / .unavailable --> R[TerminalDisconnectedOverlay]
Loading

Reviews (7): Last reviewed commit: "Add terminal loading retry state" | Re-trigger Greptile

Comment on lines +45 to +117
value: "\(resolvedMacName) · \(connectionStatus.label)",
tone: tone(for: connectionStatus)
),
TerminalLoadingDiagnosticsRow(
id: "terminals",
label: L10n.string("mobile.terminal.loading.terminals", defaultValue: "Terminals"),
value: terminalStatusText(count: terminalCount),
tone: terminalCount > 0 ? .good : .pending
),
TerminalLoadingDiagnosticsRow(
id: "tailscale",
label: L10n.string("mobile.terminal.loading.tailscale", defaultValue: "Tailscale"),
value: tailnetStatusText(tailnetStatus),
tone: tone(for: tailnetStatus)
),
TerminalLoadingDiagnosticsRow(
id: "route",
label: L10n.string("mobile.terminal.loading.route", defaultValue: "Route"),
value: routeText(activeRoute: activeRoute, storedRouteDescription: storedRouteDescription),
tone: activeRoute == nil && Self.nonEmpty(storedRouteDescription) == nil ? .warning : .neutral
),
]

if let detail = Self.nonEmpty(connectionErrorGuidance) ?? Self.nonEmpty(connectionError) {
rows.append(TerminalLoadingDiagnosticsRow(
id: "network",
label: L10n.string("mobile.terminal.loading.network", defaultValue: "Network"),
value: detail,
tone: .warning
))
}

return Self(
title: L10n.string("mobile.terminal.loading.title", defaultValue: "Loading terminals"),
message: String(
format: L10n.string(
"mobile.terminal.loading.messageFormat",
defaultValue: "Waiting for %@ to send terminal metadata for this workspace."
),
resolvedMacName
),
rows: rows
)
}

private static func nonEmpty(_ value: String?) -> String? {
let trimmed = value?.trimmingCharacters(in: .whitespacesAndNewlines)
return trimmed?.isEmpty == false ? trimmed : nil
}

private static func terminalStatusText(count: Int) -> String {
guard count > 0 else {
return L10n.string("mobile.terminal.loading.terminalsWaiting", defaultValue: "No terminal list yet")
}
return L10n.terminalCount(count)
}

private static func tailnetStatusText(_ status: TailnetStatus?) -> String {
switch status {
case .active:
return L10n.string("mobile.terminal.loading.tailscale.active", defaultValue: "Active")
case .inactiveOrNotInstalled:
return L10n.string("mobile.terminal.loading.tailscale.inactive", defaultValue: "Off or not installed")
case .unknown:
return L10n.string("mobile.terminal.loading.tailscale.unknown", defaultValue: "Unknown")
case nil:
return L10n.string("mobile.terminal.loading.tailscale.notChecked", defaultValue: "Not checked")
}
}

private static func routeText(activeRoute: CmxAttachRoute?, storedRouteDescription: String?) -> String {
if let activeRoute {
return "\(routeKindText(activeRoute.kind)) · \(endpointText(activeRoute.endpoint))"

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 Hardcoded composite separators bypass localization for two display strings

Both the mac-row value ("\(resolvedMacName) · \(connectionStatus.label)", line 45) and the active-route value ("\(routeKindText(activeRoute.kind)) · \(endpointText(activeRoute.endpoint))", line 117) embed the · separator and the name–status ordering directly via string interpolation, so translators can't change the layout or separator for a given locale. This is inconsistent with the stored-route case immediately below, which already uses a proper format key (mobile.terminal.loading.routeStoredFormat → "Saved route · %@") and with the message field (mobile.terminal.loading.messageFormat → "Waiting for %@ to…"). Both of these composite strings need format keys in the catalog alongside entries for every locale the catalog already covers.

Rule Used: Flag production user-facing text that is not fully... (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 9ef9630451

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@lawrencecchen
lawrencecchen force-pushed the task-show-terminal-loading-state branch from 9ef9630 to 5e8564a Compare July 2, 2026 08:16

@cubic-dev-ai cubic-dev-ai 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.

2 issues found and verified against the latest diff

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5e8564af2e

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread Resources/Localizable.xcstrings

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6c1f8454fc

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +250 to +251
.lineLimit(1)
.truncationMode(.middle)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Let network guidance wrap

When row.value contains connectionErrorGuidance, several existing guidance strings are full sentences such as the reachability/Tailscale instructions, so forcing every value to one line with middle truncation hides the actual next steps on an iPhone-width overlay. Consider allowing the network/warning value to wrap or rendering guidance below the label while keeping short route/status rows single-line.

Useful? React with 👍 / 👎.

@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
`@Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TerminalLoadingDiagnosticsOverlay.swift`:
- Around line 44-52: The localized template formatting in
TerminalLoadingDiagnosticsOverlay uses String(format:) with values returned from
L10n.string, which should be changed to locale-aware formatting. Update the
affected value builders in TerminalLoadingDiagnosticsOverlay (including the
macStatusFormat, messageFormat, routeActiveFormat, and routeStoredFormat usages)
to use String.localizedStringWithFormat instead of String(format:), while
keeping the same labels and resolved arguments. Use the existing formatting
sites in the overlay as the guide and preserve the current localized strings and
argument order handling.
🪄 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: c0e74517-ad8d-4232-a0c8-5f9747b2afb7

📥 Commits

Reviewing files that changed from the base of the PR and between fa21e6a and 6c1f845.

📒 Files selected for processing (5)
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TerminalLoadingDiagnosticsOverlay.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView.swift
  • Packages/iOS/CmuxMobileShellUI/Tests/CmuxMobileShellUITests/TerminalLoadingDiagnosticsModelTests.swift
  • Resources/Localizable.xcstrings
  • ios/cmux/Resources/Localizable.xcstrings

@cubic-dev-ai cubic-dev-ai 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.

1 issue found across 5 files (changes from recent commits).

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 337c98e2fa

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@cubic-dev-ai cubic-dev-ai 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.

1 issue found across 2 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

@lawrencecchen
lawrencecchen force-pushed the task-show-terminal-loading-state branch from 22407d0 to db487aa Compare July 3, 2026 03:56
@lawrencecchen
lawrencecchen force-pushed the task-show-terminal-loading-state branch 3 times, most recently from 7ba7336 to 0e218ae Compare July 3, 2026 04:09

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

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/Tests/CmuxMobileShellUITests/TerminalLoadingDiagnosticsModelTests.swift (1)

55-74: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

No assertion on title/message for disconnected states.

inactiveTailscaleAndSavedRouteSurfaceNetworkGuidance exercises connectionStatus: .unavailable with connectionError/connectionErrorGuidance set, but only asserts row values/tones — it never checks model.title/model.message/model.isLoading. This is exactly the combination that (per the sibling comment on TerminalLoadingDiagnosticsModel.swift) currently falls through to the generic "Loading terminals" copy with isLoading == false. Adding those assertions here would have caught the header/isLoading mismatch for disconnected Macs.

🤖 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/Tests/CmuxMobileShellUITests/TerminalLoadingDiagnosticsModelTests.swift`
around lines 55 - 74, Add coverage in TerminalLoadingDiagnosticsModelTests by
asserting the header state for the disconnected case in
inactiveTailscaleAndSavedRouteSurfaceNetworkGuidance, not just the rows. Verify
TerminalLoadingDiagnosticsModel’s title, message, and isLoading when
connectionStatus is .unavailable with connectionError and
connectionErrorGuidance set, so this test catches the fallback-to-loading copy
and false isLoading mismatch.
🤖 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/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TerminalLoadingDiagnosticsOverlay.swift`:
- Around line 143-169: The readiness check in updateTerminalMetadataDeadline()
is driven by a fixed sleep instead of an actual metadata/state signal. Replace
the ContinuousClock().sleep(for:) timeout gating with a real cancellation-aware
readiness source from the workspace/session flow, such as a state transition,
async sequence, notification, or callback that indicates terminal metadata has
settled. Keep the existing deadlineTaskID and terminalMetadataTimedOut behavior,
but make the timeout depend on that real signal rather than a magic delay.

In
`@Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView`+TerminalLoadingDiagnostics.swift:
- Around line 41-45: The refreshLoadingDiagnosticsConnection() method currently
starts an unstored fire-and-forget Task for store.reconnectOrRefresh(), so
repeated calls can overlap reconnect attempts. Update the
WorkspaceDetailView+TerminalLoadingDiagnostics flow to give this work a
meaningful lifecycle by storing the task on the owning view model/store,
cancelling any in-flight task before starting a new one, or otherwise guarding
against duplicate reconnect/refresh calls. Keep the fix localized around
refreshLoadingDiagnosticsConnection() and the reconnectOrRefresh path so only
one refresh operation can run at a time.

---

Outside diff comments:
In
`@Packages/iOS/CmuxMobileShellUI/Tests/CmuxMobileShellUITests/TerminalLoadingDiagnosticsModelTests.swift`:
- Around line 55-74: Add coverage in TerminalLoadingDiagnosticsModelTests by
asserting the header state for the disconnected case in
inactiveTailscaleAndSavedRouteSurfaceNetworkGuidance, not just the rows. Verify
TerminalLoadingDiagnosticsModel’s title, message, and isLoading when
connectionStatus is .unavailable with connectionError and
connectionErrorGuidance set, so this test catches the fallback-to-loading copy
and false isLoading mismatch.
🪄 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: da973560-a757-4830-b7d6-a5d2157b37f9

📥 Commits

Reviewing files that changed from the base of the PR and between 337c98e and 3ac0138.

📒 Files selected for processing (8)
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TerminalLoadingDiagnosticsModel.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TerminalLoadingDiagnosticsOverlay.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TerminalLoadingDiagnosticsRow.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView+TerminalLoadingDiagnostics.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView.swift
  • Packages/iOS/CmuxMobileShellUI/Tests/CmuxMobileShellUITests/TerminalLoadingDiagnosticsModelTests.swift
  • Resources/Localizable.xcstrings
  • ios/cmux/Resources/Localizable.xcstrings

Comment on lines +143 to +169

private var deadlineTaskID: String {
[
workspace.id.rawValue,
String(workspace.terminals.count),
connectionStatusKey(effectiveConnectionStatus),
String(refreshGeneration),
].joined(separator: ":")
}

private func updateTerminalMetadataDeadline() async {
terminalMetadataTimedOut = false
guard effectiveConnectionStatus == .connected,
workspace.terminals.isEmpty else {
return
}
do {
try await ContinuousClock().sleep(for: Self.terminalMetadataTimeout)
} catch {
return
}
guard effectiveConnectionStatus == .connected,
workspace.terminals.isEmpty else {
return
}
terminalMetadataTimedOut = true
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🔵 Trivial | ⚖️ Poor tradeoff

Sleep-based readiness wait for terminal metadata.

updateTerminalMetadataDeadline() uses ContinuousClock().sleep(for:) to decide when to show "no terminals yet" messaging. Per coding guidelines for non-test Swift files, readiness waits should be driven by "a real cancellation-aware scheduler, timer abstraction, async sequence, callback, notification, or state transition" rather than a fixed sleep gating a data-availability decision — even when wrapped in a cancellable .task(id:). Consider deriving the timeout from an actual metadata-fetch-settled signal (e.g., a state transition emitted by the connection/session store) instead of a magic 10s sleep, so this doesn't race with slow/flaky metadata fetches.

As per coding guidelines: "Do not use Task.sleep in production Swift code just because it is inside an async function... retry backoff, keepalive loops, readiness waits, delayed dispatch, and polling must use a real cancellation-aware scheduler, timer abstraction, async sequence, callback, notification, or state 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/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TerminalLoadingDiagnosticsOverlay.swift`
around lines 143 - 169, The readiness check in updateTerminalMetadataDeadline()
is driven by a fixed sleep instead of an actual metadata/state signal. Replace
the ContinuousClock().sleep(for:) timeout gating with a real cancellation-aware
readiness source from the workspace/session flow, such as a state transition,
async sequence, notification, or callback that indicates terminal metadata has
settled. Keep the existing deadlineTaskID and terminalMetadataTimedOut behavior,
but make the timeout depend on that real signal rather than a magic delay.

Source: Coding guidelines

Comment on lines +41 to +45
func refreshLoadingDiagnosticsConnection() {
Task {
await store.reconnectOrRefresh()
}
}

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 | 🔵 Trivial | ⚖️ Poor tradeoff

Untracked fire-and-forget Task for reconnect/refresh.

refreshLoadingDiagnosticsConnection() launches a new, unstored Task on every call (each Refresh tap), with no cancellation of a prior in-flight task. If reconnectOrRefresh() isn't internally idempotent/de-duplicated, repeated taps could trigger overlapping reconnect attempts.

As per coding guidelines: "Flag fire-and-forget Task { ... } work with meaningful lifecycle that is not stored, cancelled, or tied to a caller-owned operation."

Consider storing the task (e.g., on the store/view model) and cancelling/ignoring a new request while one is in flight.

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

In
`@Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView`+TerminalLoadingDiagnostics.swift
around lines 41 - 45, The refreshLoadingDiagnosticsConnection() method currently
starts an unstored fire-and-forget Task for store.reconnectOrRefresh(), so
repeated calls can overlap reconnect attempts. Update the
WorkspaceDetailView+TerminalLoadingDiagnostics flow to give this work a
meaningful lifecycle by storing the task on the owning view model/store,
cancelling any in-flight task before starting a new one, or otherwise guarding
against duplicate reconnect/refresh calls. Keep the fix localized around
refreshLoadingDiagnosticsConnection() and the reconnectOrRefresh path so only
one refresh operation can run at a time.

Source: Coding guidelines

@lawrencecchen
lawrencecchen force-pushed the task-show-terminal-loading-state branch 2 times, most recently from adcf168 to 91caa2d Compare July 3, 2026 04:19

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

Actionable comments posted: 3

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/WorkspaceDetailView.swift (1)

274-288: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Duplicate reconnect/switch logic now that a shared helper exists.

The TerminalDisconnectedOverlay retry closure (lines 278-286) re-implements the exact same switchToMac(...)/reconnectOrRefresh() sequence that WorkspaceDetailView+TerminalLoadingDiagnostics.swift's refreshLoadingDiagnosticsConnection() now encapsulates and that is wired to the new TerminalLoadingDiagnosticsOverlay's refresh button. Two separate call sites maintaining identical reconnect logic risks drift (e.g., if one is fixed for the fire-and-forget-Task concern above but the other isn't).

As per coding guidelines' shared behavior policy: "When a behavior is exposed through multiple entrypoints... implement one shared action/model path and verify every entrypoint that should invoke it. Do not patch one surface while leaving the others with duplicated logic."

♻️ Suggested consolidation
         .overlay {
             // Show a reconnecting/offline state instead of a black terminal.
             if loadingDiagnosticsConnectionStatus != .connected {
                 TerminalDisconnectedOverlay(status: loadingDiagnosticsConnectionStatus, host: host) {
-                    Task {
-                        if let macDeviceID = workspace.macDeviceID,
-                           !macDeviceID.isEmpty,
-                           await store.switchToMac(macDeviceID: macDeviceID) {
-                            return
-                        }
-                        await store.reconnectOrRefresh()
-                    }
+                    refreshLoadingDiagnosticsConnection()
                 }
             }
         }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView.swift`
around lines 274 - 288, The TerminalDisconnectedOverlay retry closure is
duplicating the same reconnect/switch flow already centralized in
refreshLoadingDiagnosticsConnection(). Update WorkspaceDetailView to call that
shared helper from the overlay action instead of re-implementing the Task with
switchToMac(macDeviceID:) and reconnectOrRefresh(), so
TerminalDisconnectedOverlay and TerminalLoadingDiagnosticsOverlay both use the
same behavior path.

Source: Coding guidelines

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

Inline comments:
In
`@Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TerminalLoadingDiagnosticsModel.swift`:
- Around line 5-43: Mark TerminalLoadingDiagnosticsModel as nonisolated because
it is a pure value type and should not implicitly inherit MainActor isolation
under Swift 6. Update the struct declaration itself, and keep both initializers
in place while ensuring the TerminalLoadingDiagnosticsModelBuilder path and the
direct property initializer remain free of actor affinity so the model can be
constructed off-main without unnecessary hops.

In
`@Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TerminalLoadingDiagnosticsRow.swift`:
- Around line 2-7: `TerminalLoadingDiagnosticsRow` is a pure value
`Identifiable` model that may implicitly inherit `@MainActor` under Swift 6
isolation. Update the struct declaration itself to be explicitly nonisolated so
it can be passed freely between actor contexts without unnecessary hops. Keep
the change scoped to `TerminalLoadingDiagnosticsRow` and preserve its current
stored properties and protocol conformances.

In
`@Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView`+TerminalLoadingDiagnostics.swift:
- Around line 39-45: The loading diagnostics properties currently hide
connection error and guidance whenever workspace.macDeviceID is nil, which
causes the overlay to lose network diagnostics for that workspace shape. Update
WorkspaceDetailView+TerminalLoadingDiagnostics so
loadingDiagnosticsConnectionError and loadingDiagnosticsConnectionErrorGuidance
use the same nil/empty fallback logic as activeLoadingDiagnosticsRoute, instead
of requiring a non-nil macDeviceID, and keep the existing store.connectionError
and store.connectionErrorGuidance values visible when appropriate.

---

Outside diff comments:
In
`@Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView.swift`:
- Around line 274-288: The TerminalDisconnectedOverlay retry closure is
duplicating the same reconnect/switch flow already centralized in
refreshLoadingDiagnosticsConnection(). Update WorkspaceDetailView to call that
shared helper from the overlay action instead of re-implementing the Task with
switchToMac(macDeviceID:) and reconnectOrRefresh(), so
TerminalDisconnectedOverlay and TerminalLoadingDiagnosticsOverlay both use the
same behavior path.
🪄 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: b846549b-3fbf-45c2-a743-aec477e3b476

📥 Commits

Reviewing files that changed from the base of the PR and between 3ac0138 and 0e218ae.

📒 Files selected for processing (10)
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TerminalLoadingDiagnosticsModel.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TerminalLoadingDiagnosticsModelBuilder.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TerminalLoadingDiagnosticsOverlay.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TerminalLoadingDiagnosticsRow.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TerminalLoadingDiagnosticsTone.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView+TerminalLoadingDiagnostics.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView.swift
  • Packages/iOS/CmuxMobileShellUI/Tests/CmuxMobileShellUITests/TerminalLoadingDiagnosticsModelTests.swift
  • Resources/Localizable.xcstrings
  • ios/cmux/Resources/Localizable.xcstrings

Comment on lines +5 to +43
struct TerminalLoadingDiagnosticsModel: Equatable {
let title: String
let message: String
let rows: [TerminalLoadingDiagnosticsRow]
let isLoading: Bool

init(
workspaceName: String,
terminalCount: Int,
macName: String?,
connectionStatus: MobileMacConnectionStatus,
tailnetStatus: TailnetStatus?,
activeRoute: CmxAttachRoute?,
storedRouteDescription: String?,
connectionError: String?,
connectionErrorGuidance: String?,
loadingTimedOut: Bool = false
) {
self = TerminalLoadingDiagnosticsModelBuilder(
workspaceName: workspaceName,
terminalCount: terminalCount,
macName: macName,
connectionStatus: connectionStatus,
tailnetStatus: tailnetStatus,
activeRoute: activeRoute,
storedRouteDescription: storedRouteDescription,
connectionError: connectionError,
connectionErrorGuidance: connectionErrorGuidance,
loadingTimedOut: loadingTimedOut
).makeModel()
}

init(title: String, message: String, rows: [TerminalLoadingDiagnosticsRow], isLoading: Bool) {
self.title = title
self.message = message
self.rows = rows
self.isLoading = isLoading
}
}

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 | 🔵 Trivial | ⚡ Quick win

Consider marking this pure value struct nonisolated.

TerminalLoadingDiagnosticsModel is a plain data struct with no UI/actor-affinity, but under Swift 6 MainActor-by-default isolation it can implicitly inherit @MainActor, coupling a value-only type to the main actor unnecessarily and forcing hops when built off-main.

♻️ Suggested fix
-struct TerminalLoadingDiagnosticsModel: Equatable {
+nonisolated struct TerminalLoadingDiagnosticsModel: Equatable {

As per coding guidelines, "Report a failure when the diff introduces or materially expands ... pure value model structs that remain implicitly @MainActor when they should be marked nonisolated."

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
struct TerminalLoadingDiagnosticsModel: Equatable {
let title: String
let message: String
let rows: [TerminalLoadingDiagnosticsRow]
let isLoading: Bool
init(
workspaceName: String,
terminalCount: Int,
macName: String?,
connectionStatus: MobileMacConnectionStatus,
tailnetStatus: TailnetStatus?,
activeRoute: CmxAttachRoute?,
storedRouteDescription: String?,
connectionError: String?,
connectionErrorGuidance: String?,
loadingTimedOut: Bool = false
) {
self = TerminalLoadingDiagnosticsModelBuilder(
workspaceName: workspaceName,
terminalCount: terminalCount,
macName: macName,
connectionStatus: connectionStatus,
tailnetStatus: tailnetStatus,
activeRoute: activeRoute,
storedRouteDescription: storedRouteDescription,
connectionError: connectionError,
connectionErrorGuidance: connectionErrorGuidance,
loadingTimedOut: loadingTimedOut
).makeModel()
}
init(title: String, message: String, rows: [TerminalLoadingDiagnosticsRow], isLoading: Bool) {
self.title = title
self.message = message
self.rows = rows
self.isLoading = isLoading
}
}
nonisolated struct TerminalLoadingDiagnosticsModel: Equatable {
let title: String
let message: String
let rows: [TerminalLoadingDiagnosticsRow]
let isLoading: Bool
init(
workspaceName: String,
terminalCount: Int,
macName: String?,
connectionStatus: MobileMacConnectionStatus,
tailnetStatus: TailnetStatus?,
activeRoute: CmxAttachRoute?,
storedRouteDescription: String?,
connectionError: String?,
connectionErrorGuidance: String?,
loadingTimedOut: Bool = false
) {
self = TerminalLoadingDiagnosticsModelBuilder(
workspaceName: workspaceName,
terminalCount: terminalCount,
macName: macName,
connectionStatus: connectionStatus,
tailnetStatus: tailnetStatus,
activeRoute: activeRoute,
storedRouteDescription: storedRouteDescription,
connectionError: connectionError,
connectionErrorGuidance: connectionErrorGuidance,
loadingTimedOut: loadingTimedOut
).makeModel()
}
init(title: String, message: String, rows: [TerminalLoadingDiagnosticsRow], isLoading: Bool) {
self.title = title
self.message = message
self.rows = rows
self.isLoading = isLoading
}
}
🤖 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/TerminalLoadingDiagnosticsModel.swift`
around lines 5 - 43, Mark TerminalLoadingDiagnosticsModel as nonisolated because
it is a pure value type and should not implicitly inherit MainActor isolation
under Swift 6. Update the struct declaration itself, and keep both initializers
in place while ensuring the TerminalLoadingDiagnosticsModelBuilder path and the
direct property initializer remain free of actor affinity so the model can be
constructed off-main without unnecessary hops.

Source: Coding guidelines

Comment on lines +2 to +7
struct TerminalLoadingDiagnosticsRow: Equatable, Identifiable {
let id: String
let label: String
let value: String
let tone: TerminalLoadingDiagnosticsTone
}

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 | 🔵 Trivial | ⚡ Quick win

Identifiable value struct should likely be nonisolated.

This new Identifiable value struct is exactly the pattern called out by the actor-isolation guideline: under Swift 6 MainActor-by-default isolation, an Identifiable pure-data struct can implicitly inherit @MainActor, forcing actor hops for what should be a freely-passable value type (e.g. between a background builder and ForEach rows).

♻️ Suggested fix
-struct TerminalLoadingDiagnosticsRow: Equatable, Identifiable {
+nonisolated struct TerminalLoadingDiagnosticsRow: Equatable, Identifiable {

As per coding guidelines: "Report a failure when the diff introduces or materially expands Codable, Identifiable, Sendable, or pure value model structs that remain implicitly @MainActor when they should be marked nonisolated."

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
struct TerminalLoadingDiagnosticsRow: Equatable, Identifiable {
let id: String
let label: String
let value: String
let tone: TerminalLoadingDiagnosticsTone
}
nonisolated struct TerminalLoadingDiagnosticsRow: Equatable, Identifiable {
let id: String
let label: String
let value: String
let tone: TerminalLoadingDiagnosticsTone
}
🤖 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/TerminalLoadingDiagnosticsRow.swift`
around lines 2 - 7, `TerminalLoadingDiagnosticsRow` is a pure value
`Identifiable` model that may implicitly inherit `@MainActor` under Swift 6
isolation. Update the struct declaration itself to be explicitly nonisolated so
it can be passed freely between actor contexts without unnecessary hops. Keep
the change scoped to `TerminalLoadingDiagnosticsRow` and preserve its current
stored properties and protocol conformances.

Source: Coding guidelines

@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/WorkspaceDetailView.swift (2)

274-291: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Disconnected-overlay retry closure duplicates refreshLoadingDiagnosticsConnection() logic.

The inline closure here (switchToMac then reconnectOrRefresh) reimplements the exact same sequence that the new refreshLoadingDiagnosticsConnection() helper (in WorkspaceDetailView+TerminalLoadingDiagnostics.swift) already encapsulates and is used by the loading overlay's Refresh button. Route both call sites through the single helper to keep the reconnect behavior consistent and avoid future drift between the two entrypoints.

♻️ Proposed fix
             if loadingDiagnosticsConnectionStatus != .connected {
                 TerminalDisconnectedOverlay(
                     status: loadingDiagnosticsConnectionStatus,
                     host: workspace.macDisplayName ?? host
                 ) {
-                    Task {
-                        if let macDeviceID = workspace.macDeviceID,
-                           !macDeviceID.isEmpty,
-                           await store.switchToMac(macDeviceID: macDeviceID) {
-                            return
-                        }
-                        await store.reconnectOrRefresh()
-                    }
+                    refreshLoadingDiagnosticsConnection()
                 }
             }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView.swift`
around lines 274 - 291, The disconnected-overlay retry action in
WorkspaceDetailView is duplicating the reconnect sequence already centralized in
refreshLoadingDiagnosticsConnection() from
WorkspaceDetailView+TerminalLoadingDiagnostics.swift. Update the
TerminalDisconnectedOverlay closure to call that helper instead of inlining
switchToMac(macDeviceID:) followed by reconnectOrRefresh(), so both retry
entrypoints share the same behavior and stay in sync.

245-291: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Use the same scoped status for the pill and the overlay. MobileMacConnectionStatusPill still uses the raw connectionStatus, while the full-screen overlay uses loadingDiagnosticsConnectionStatus; when the workspace is tied to a different Mac, the overlay can show .unavailable while the pill still shows the workspace’s reconnecting/offline state. Pass the scoped status through here, or hide the pill in the unavailable case.

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

In
`@Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView.swift`
around lines 245 - 291, The status pill and the disconnected overlay are using
different connection states, which can make them disagree when the workspace is
scoped to a different Mac. In WorkspaceDetailView, align
MobileMacConnectionStatusPill with the same scoped value used by the full-screen
overlay (loadingDiagnosticsConnectionStatus), or suppress the pill when that
status is .unavailable. Keep the change localized to the existing overlay/pill
wiring so both UI states stay consistent.
♻️ Duplicate comments (3)
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TerminalLoadingDiagnosticsOverlay.swift (1)

153-169: 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift

Sleep-based readiness wait to gate "no terminals yet" state.

updateTerminalMetadataDeadline() still uses ContinuousClock().sleep(for:) to decide when to flip terminalMetadataTimedOut. Same concern raised in a prior review pass on this file: this races with slow/flaky metadata fetches and doesn't derive from a real settled signal.

As per coding guidelines: "Do not use Task.sleep in production Swift code just because it is inside an async function... retry backoff, keepalive loops, readiness waits, delayed dispatch, and polling must use a real cancellation-aware scheduler, timer abstraction, async sequence, callback, notification, or state 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/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TerminalLoadingDiagnosticsOverlay.swift`
around lines 153 - 169, The readiness gate in updateTerminalMetadataDeadline()
still relies on a fixed sleep, which can race with slow or flaky terminal
metadata loading. Replace the ContinuousClock().sleep(for:) wait in
TerminalLoadingDiagnosticsOverlay with a real cancellation-aware readiness
signal or state transition tied to workspace/terminal metadata changes, and keep
the existing terminalMetadataTimedOut updates based on that signal instead of
elapsed time.

Source: Coding guidelines

Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView+TerminalLoadingDiagnostics.swift (2)

46-54: 🩺 Stability & Availability | 🟡 Minor | ⚖️ Poor tradeoff

Untracked fire-and-forget Task for reconnect/refresh.

Every call to refreshLoadingDiagnosticsConnection() (each Refresh tap) spawns a new unstored Task, with no cancellation of a prior in-flight one. Repeated taps could overlap switchToMac/reconnectOrRefresh calls.

As per coding guidelines: "Flag fire-and-forget Task { ... } work with meaningful lifecycle that is not stored, cancelled, or tied to a caller-owned operation."

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

In
`@Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView`+TerminalLoadingDiagnostics.swift
around lines 46 - 54, The refreshLoadingDiagnosticsConnection() method is
launching a fire-and-forget Task without any lifecycle control, so repeated
refreshes can overlap. Update this logic to keep the Task tied to a meaningful
lifecycle by storing it on the WorkspaceDetailView+TerminalLoadingDiagnostics
state, canceling any in-flight task before starting a new one, and/or reusing a
caller-owned async path. Use the refreshLoadingDiagnosticsConnection() entry
point and the existing switchToMac(macDeviceID:) and reconnectOrRefresh() calls
as the place to add cancellation and serialization.

Source: Coding guidelines


38-44: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Connection error/guidance still dropped for nil macDeviceID workspaces.

loadingDiagnosticsConnectionError/loadingDiagnosticsConnectionErrorGuidance gate on loadingDiagnosticsMatchesForegroundMac(workspace.macDeviceID), which returns false whenever macDeviceID is nil/empty (see the guard at line 57-60). This means the overlay silently loses connection error/guidance for that workspace shape, same gap flagged in a prior review pass. activeLoadingDiagnosticsRoute (lines 27-36) already has the correct nil-fallback pattern (falling back to store.activeRoute when connected) — mirror that here instead of unconditionally returning nil.

🛠️ Proposed fix
     var loadingDiagnosticsConnectionError: String? {
-        loadingDiagnosticsMatchesForegroundMac(workspace.macDeviceID) ? store.connectionError : nil
+        guard let macDeviceID = workspace.macDeviceID, !macDeviceID.isEmpty else {
+            return loadingDiagnosticsConnectionStatus == .connected ? store.connectionError : nil
+        }
+        return loadingDiagnosticsMatchesForegroundMac(macDeviceID) ? store.connectionError : nil
     }

     var loadingDiagnosticsConnectionErrorGuidance: String? {
-        loadingDiagnosticsMatchesForegroundMac(workspace.macDeviceID) ? store.connectionErrorGuidance : nil
+        guard let macDeviceID = workspace.macDeviceID, !macDeviceID.isEmpty else {
+            return loadingDiagnosticsConnectionStatus == .connected ? store.connectionErrorGuidance : nil
+        }
+        return loadingDiagnosticsMatchesForegroundMac(macDeviceID) ? store.connectionErrorGuidance : nil
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView`+TerminalLoadingDiagnostics.swift
around lines 38 - 44, The connection error and guidance are still being dropped
for workspaces with a nil or empty macDeviceID because
loadingDiagnosticsConnectionError and loadingDiagnosticsConnectionErrorGuidance
only return values when
loadingDiagnosticsMatchesForegroundMac(workspace.macDeviceID) is true. Update
the WorkspaceDetailView+TerminalLoadingDiagnostics logic to mirror the
nil-fallback behavior used by activeLoadingDiagnosticsRoute: keep using the
foreground-Mac check when macDeviceID is present, but fall back to
store.connectionError and store.connectionErrorGuidance for the nil/empty
macDeviceID workspace shape instead of returning nil.
🤖 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/WorkspaceDetailView.swift`:
- Around line 274-291: The disconnected-overlay retry action in
WorkspaceDetailView is duplicating the reconnect sequence already centralized in
refreshLoadingDiagnosticsConnection() from
WorkspaceDetailView+TerminalLoadingDiagnostics.swift. Update the
TerminalDisconnectedOverlay closure to call that helper instead of inlining
switchToMac(macDeviceID:) followed by reconnectOrRefresh(), so both retry
entrypoints share the same behavior and stay in sync.
- Around line 245-291: The status pill and the disconnected overlay are using
different connection states, which can make them disagree when the workspace is
scoped to a different Mac. In WorkspaceDetailView, align
MobileMacConnectionStatusPill with the same scoped value used by the full-screen
overlay (loadingDiagnosticsConnectionStatus), or suppress the pill when that
status is .unavailable. Keep the change localized to the existing overlay/pill
wiring so both UI states stay consistent.

---

Duplicate comments:
In
`@Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TerminalLoadingDiagnosticsOverlay.swift`:
- Around line 153-169: The readiness gate in updateTerminalMetadataDeadline()
still relies on a fixed sleep, which can race with slow or flaky terminal
metadata loading. Replace the ContinuousClock().sleep(for:) wait in
TerminalLoadingDiagnosticsOverlay with a real cancellation-aware readiness
signal or state transition tied to workspace/terminal metadata changes, and keep
the existing terminalMetadataTimedOut updates based on that signal instead of
elapsed time.

In
`@Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView`+TerminalLoadingDiagnostics.swift:
- Around line 46-54: The refreshLoadingDiagnosticsConnection() method is
launching a fire-and-forget Task without any lifecycle control, so repeated
refreshes can overlap. Update this logic to keep the Task tied to a meaningful
lifecycle by storing it on the WorkspaceDetailView+TerminalLoadingDiagnostics
state, canceling any in-flight task before starting a new one, and/or reusing a
caller-owned async path. Use the refreshLoadingDiagnosticsConnection() entry
point and the existing switchToMac(macDeviceID:) and reconnectOrRefresh() calls
as the place to add cancellation and serialization.
- Around line 38-44: The connection error and guidance are still being dropped
for workspaces with a nil or empty macDeviceID because
loadingDiagnosticsConnectionError and loadingDiagnosticsConnectionErrorGuidance
only return values when
loadingDiagnosticsMatchesForegroundMac(workspace.macDeviceID) is true. Update
the WorkspaceDetailView+TerminalLoadingDiagnostics logic to mirror the
nil-fallback behavior used by activeLoadingDiagnosticsRoute: keep using the
foreground-Mac check when macDeviceID is present, but fall back to
store.connectionError and store.connectionErrorGuidance for the nil/empty
macDeviceID workspace shape instead of returning nil.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: d49c3aa7-6ca9-4b1a-bb29-3cc1c83ade3e

📥 Commits

Reviewing files that changed from the base of the PR and between 0e218ae and 2858674.

📒 Files selected for processing (10)
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TerminalLoadingDiagnosticsModel.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TerminalLoadingDiagnosticsModelBuilder.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TerminalLoadingDiagnosticsOverlay.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TerminalLoadingDiagnosticsRow.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TerminalLoadingDiagnosticsTone.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView+TerminalLoadingDiagnostics.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView.swift
  • Packages/iOS/CmuxMobileShellUI/Tests/CmuxMobileShellUITests/TerminalLoadingDiagnosticsModelTests.swift
  • Resources/Localizable.xcstrings
  • ios/cmux/Resources/Localizable.xcstrings

@lawrencecchen lawrencecchen added the stale-revisit Closed after 30+ days without activity; preserved for possible revisit or reopening. label Sep 23, 2026
@github-project-automation github-project-automation Bot moved this from Todo to Done in cmux backlog Sep 23, 2026

This branch was successfully deployed

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

Labels

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants