Skip to content

Fix iOS deleted computer recovery empty state - #8712

Merged
azooz2003-bit merged 6 commits into
mainfrom
fix-ios-recover-empty-state
Jul 23, 2026
Merged

azooz2003-bit merged 6 commits into
mainfrom
fix-ios-recover-empty-state

Conversation

@azooz2003-bit

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

Copy link
Copy Markdown
Collaborator

Summary

  • show the deleted-computer recovery action on the iOS No devices disconnected empty state
  • keep Add Computer available, but stop auto-presenting it when a recoverable deleted Mac marker exists
  • share the recovery button/footer between the Computers screen and the empty state so both use the same account-scoped restore path

Verification

  • git diff --check
  • jq empty ios/cmux/Resources/Localizable.xcstrings
  • swift test --filter IrohZeroTouchDiscoveryTests from Packages/iOS/CmuxMobileShell
  • xcodebuild build -workspace ios/cmux.xcworkspace -scheme CmuxMobileShellUI -destination platform=iOS Simulator,id=9369A943-7279-4969-A73D-5AA001A458AC -derivedDataPath /tmp/cmux-recov-empty-ui-dd
  • xcodebuild build -workspace ios/cmux.xcworkspace -scheme cmux-ios -destination platform=iOS Simulator,id=9369A943-7279-4969-A73D-5AA001A458AC -derivedDataPath /tmp/cmux-recov-empty-app-build-dd CODE_SIGNING_ALLOWED=NO

Notes

  • A focused app-scheme test invocation compiled the new test but the test target is currently blocked by an unrelated TerminalSurfaceMountOwnershipTests compile error on main: missing terminalFolderTapEnabled.

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


Summary by cubic

Promotes deleted-computer recovery in the iOS disconnected empty state and makes Add Computer secondary. Keeps the recovery button busy through reload, adds explicit results, and only auto‑opens Add Computer after a successful paired‑Mac load.

  • Bug Fixes

    • Show recovery in the disconnected empty state; make Add Computer secondary and don’t auto‑present when recovery is available. Auto‑present only when pairedMacLoadState == .loaded, the list is empty, and no recovery exists; hide recovery and suppress auto‑present on .failed.
    • Keep the recovery button disabled with inline progress through reload; block duplicate taps; return .staleScope on sign‑out/team switch; on .notFound reload paired Macs + registry and show a localized error.
  • Refactors

    • Centralized state in MobileShellComposite (isRecoveringDeletedComputer, hasRecoverableDeletedComputers, pairedMacLoadState) and made recoverForgottenIrohMacFromAccount() return MobileDeletedComputerRecoveryResult (.recovered, .notFound, .alreadyInProgress, .staleScope). Extracted shared DeletedComputerRecoveryButton and DeletedComputerRecoveryFooter used in both DeviceTreeView and DisconnectedWorkspaceShellView.

Written for commit 946ffa2. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features
    • Added deleted-computer recovery controls to the disconnected-workspace empty state, including a prominent recovery button with inline progress and a localized explanatory footer.
  • Bug Fixes
    • Improved the recovery flow by preventing re-entry, disabling interaction during recovery, and showing a localized failure alert when recovery can’t be completed.
    • Ensured recovery availability and the auto “add device” experience are correctly gated and cleared based on paired-device loading outcomes.
  • Tests
    • Added/expanded iOS UI and unit tests to verify recovery visibility, auto-add suppression, and explicit recovery result states.

@coderabbitai

coderabbitai Bot commented Jul 23, 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 reusable iOS deleted-computer recovery controls, centralizes recovery state and result handling, integrates recovery into device and disconnected-workspace views, changes add-device presentation rules, and adds tests for the updated flow.

Changes

Deleted computer recovery

Layer / File(s) Summary
Recovery state and execution
Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite.swift, Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+ForgottenMacRecovery.swift, Packages/iOS/CmuxMobileShell/Tests/CmuxMobileShellTests/IrohZeroTouchDiscoveryTests.swift
Adds explicit recovery results, tracks active recovery, prevents concurrent attempts, tracks paired-Mac loading, clears stale recoverable state after load failures, and updates recovery assertions.
Reusable recovery controls
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/DeletedComputerRecoveryButton.swift
Adds localized recovery and footer views with progress display, disabled state, failure reload handling, alerts, and cancellation.
Device and disconnected-workspace integration
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/DeviceTreeView.swift, Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/DisconnectedWorkspaceShellView.swift, Packages/iOS/CmuxMobileShellUI/Tests/CmuxMobileShellUITests/DisconnectedWorkspaceShellRecoveryTests.swift
Replaces inline device-tree recovery logic, presents recovery in disconnected-workspace states, gates automatic add-device presentation, reloads after failure, and tests the behavior.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant DisconnectedWorkspaceShellView
  participant DeletedComputerRecoveryButton
  participant CMUXMobileShellStore
  participant DeviceReload
  DisconnectedWorkspaceShellView->>DeletedComputerRecoveryButton: Render recovery action
  DeletedComputerRecoveryButton->>CMUXMobileShellStore: recoverForgottenIrohMacFromAccount()
  CMUXMobileShellStore-->>DeletedComputerRecoveryButton: Recovery result
  DeletedComputerRecoveryButton->>DeviceReload: Reload paired Macs and registry devices
  DeviceReload-->>DisconnectedWorkspaceShellView: Updated device state
Loading

Possibly related PRs

Suggested reviewers: lawrencecchen

🚥 Pre-merge checks | ✅ 23 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% 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 verification, but it omits required Demo Video, Review Trigger, and Checklist sections from the template. Add the missing template sections, rename Verification to Testing, and include the requested review trigger and checklist items.
✅ Passed checks (23 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Cmux Swift Actor Isolation ✅ Passed PASS: New recovery state stays on the existing @MainActor store, and the new SwiftUI recovery UI uses @MainActor closures/Tasks; no new shared Sendable ref or background store access.
Cmux Swift Blocking Runtime ✅ Passed PR adds no new source-side waits/sleeps/syncs/locks; only existing timer/sleep loops remain unchanged and test-only NSLock scaffolding is preexisting.
Cmux Browser Automation Off-Main ✅ Passed Diff is iOS recovery UI/composite only; no browser/socket-router symbols in changed files, and the rule targets terminal/control-socket browser automation.
Cmux Expensive Synchronous Load ✅ Passed The diff only adds async paired-Mac/registry reloads in UI task/tap paths; no agent-history, transcript/JSONL, or other sync-heavy main-actor scans were introduced.
Cmux Cache Substitution Correctness ✅ Passed The new UI only trusts pairedMacs after a fresh load state, and the recovery path uses live discovery plus stale-scope handling; no fresh read was replaced by an unchecked cache.
Cmux No Hacky Sleeps ✅ Passed PR changes only a Swift file; the runtime-no-hacky-sleeps rule is out of scope and no non-Swift sleeps/timers were added.
Cmux Algorithmic Complexity ✅ Passed Recovery scan is capped at 4, production route lists are single-element, and new UI/state checks are O(1); no scalable nested rescans were introduced.
Cmux Swift Concurrency ✅ Passed No new background queues, Combine state, callback APIs, or fire-and-forget Tasks without lifecycle were introduced; the new Task is stored and cancelled on disappear.
Cmux Swift @Concurrent ✅ Passed No concurrency-rule violation: new async UI helpers are coordination-only, and the heavy discovery/registry/paired-mac calls are actor/MainActor-isolated.
Cmux Swift Package Boundaries ✅ Passed Recovery logic lives in CmuxMobileShell/CmuxMobileShellUI package targets; the diff adds only package UI glue and tests, with no app-root domain logic leak.
Cmux Swiftpm Lockfiles ✅ Passed Diff only changes Swift source/tests; no .gitignore, Package.swift, Xcode project, or Package.resolved files were modified.
Cmux Swift Logging ✅ Passed Changed Swift diff only updates recovery-button state; no print/NSLog/Logger additions or file-scoped logging changes appear.
Cmux User-Facing Error Privacy ✅ Passed New recovery alerts and footer use only generic cmux/product copy; no upstream/vendor/internal names or raw diagnostics are exposed.
Cmux Full Internationalization ✅ Passed PASS: New Swift UI text uses L10n.string/String(localized), and the app catalog adds matching en/ja entries for all supported locales.
Cmux Swiftui State Layout ✅ Passed DeletedComputerRecoveryButton only adds @State and event-driven async handling; no ObservableObject/@published, GeometryReader, lazy row store refs, or render-time mutations were introduced.
Cmux Architecture Rethink ✅ Passed Only DeletedComputerRecoveryButton changed; it adds a local task handle to keep UI busy through reload, with no sleeps/polling/locks or split state ownership.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed Only DeletedComputerRecoveryButton.swift changed; it's an iOS SwiftUI View, with no NSWindow/NSPanel/WindowGroup or cmuxAuxiliaryWindowIdentifiers changes.
Cmux Source Artifacts ✅ Passed All changed paths are intentional Swift source/tests under Sources/Tests; no logs, caches, DerivedData, or scratch dirs were added.
Cmux No Test Or Debug Seam In Production Source ✅ Passed No new test/debug seam appears in production Sources; the added recovery UI/state is product-facing and used by app callers, not test-only accessors.
Cmux No Ambient Global State ✅ Passed No new file-scope API, mutable global state, namespace-only type, or singleton was added; the recovery logic and state live on MobileShellComposite or View structs.
Title check ✅ Passed The title clearly summarizes the main change: the iOS deleted-computer recovery empty state.
✨ 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 fix-ios-recover-empty-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.

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

🤖 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/DeletedComputerRecoveryButton.swift`:
- Around line 48-51: Centralize account-scoped recovery attempt and status
ownership in the existing store or shared coordinator instead of each
DeletedComputerRecoveryButton instance. In
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/DeletedComputerRecoveryButton.swift#L48-L51,
remove the per-instance recoveryTask and recoveryAttemptID state and render from
the authoritative snapshot. In
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/DeviceTreeView.swift#L147-L154,
bind the control to that shared recovery state and action so concurrent surfaces
reflect one cancellable recovery attempt.

In
`@Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/DisconnectedWorkspaceShellView.swift`:
- Around line 145-152: The shouldAutoPresentAddDeviceAfterLoadingSavedMacs gate
must not use stale hasRecoverableDeletedComputers data after a failed refresh.
Update the load state around loadPairedMacs and
showsDeletedComputerRecoveryAction so the recovery marker is explicitly
invalidated or marked unknown when loading fails, and only suppress automatic
Add Computer after a successful current marker lookup confirms recoverable
deleted computers.
- Around line 357-361: Update reloadAfterDeletedComputerRecovery and the
DeletedComputerRecoveryButton recovery flow so successful recoveries are not
reloaded twice: rely on recoverForgottenIrohMacFromAccount for paired Mac and
registry refreshes, while preserving any refresh required when recovery fails.

In
`@Packages/iOS/CmuxMobileShellUI/Tests/CmuxMobileShellUITests/DisconnectedWorkspaceShellRecoveryTests.swift`:
- Around line 20-28: Update
recoverableDeletedComputerSuppressesAutomaticAddComputerSheet so
loadPairedMacs() runs before setting store.hasRecoverableDeletedComputers to
true, or seed a forgotten-computer marker before loading. Preserve the assertion
that shouldAutoPresentAddDeviceAfterLoadingSavedMacs is false.
🪄 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 Plus

Run ID: b24572ce-873d-4454-a9b5-b8b5990a9f37

📥 Commits

Reviewing files that changed from the base of the PR and between 4dca828 and 952cced.

📒 Files selected for processing (4)
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/DeletedComputerRecoveryButton.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/DeviceTreeView.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/DisconnectedWorkspaceShellView.swift
  • Packages/iOS/CmuxMobileShellUI/Tests/CmuxMobileShellUITests/DisconnectedWorkspaceShellRecoveryTests.swift

@greptile-apps

greptile-apps Bot commented Jul 23, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR promotes the deleted-computer Iroh recovery action into the iOS disconnected/empty-state view and refactors the feature into a shared DeletedComputerRecoveryButton/DeletedComputerRecoveryFooter pair. It also converts recoverForgottenIrohMacFromAccount() from a Bool return to a typed MobileDeletedComputerRecoveryResult enum, centralises re-entrancy guarding in the store, adds pairedMacLoadState to prevent premature auto-add-computer on non-.loaded states, and tightens scope-staleness detection with secondaryAggregationScopeGeneration.

  • Recovery promoted to empty state: DisconnectedWorkspaceShellView now shows DeletedComputerRecoveryButton as the prominent action and demotes "Add Computer" to secondary when hasRecoverableDeletedComputers is true; auto-add-computer is gated behind pairedMacLoadState == .loaded to avoid firing during load failures or mid-reload.
  • Shared button component: DeletedComputerRecoveryButton combines the store-level isRecovering flag and its own recoveryTask to stay disabled through both the Iroh scan phase and the post-failure reload, avoiding duplicate taps across both the Computers screen and the empty state.
  • Result-typed recovery: recoverForgottenIrohMacFromAccount() returns .recovered, .notFound, .alreadyInProgress, or .staleScope; the guard at the function entry owns re-entrancy, and defer { isRecoveringDeletedComputer = false } runs when the function returns rather than when the UI task exits.

Confidence Score: 5/5

Safe to merge. The recovery logic is well-isolated on the main actor, scope-staleness checks are thorough, and the shared button correctly spans both the store-level and view-level in-progress gates.

The typed result enum eliminates the old Boolean ambiguity, pairedMacLoadState prevents premature auto-add-computer on failure or mid-reload, and secondaryAggregationScopeGeneration closes a previously open team-switch window. Test coverage is solid and the prior ordering bug in the UI tests is corrected.

No files require special attention beyond cosmetic issues already noted in prior review threads.

Important Files Changed

Filename Overview
Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+ForgottenMacRecovery.swift Converted return type to typed enum, added re-entrancy guard and defer cleanup, added secondaryAggregationScopeGeneration to the ifStillCurrent closure, and added isScopeCurrent guard after connectAccountDiscoveredIrohMac. Logic is sound and the new staleScope detection is a correctness improvement.
Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite.swift Added PairedMacLoadState enum, pairedMacLoadState and isRecoveringDeletedComputer properties, and correctly propagates .notLoaded/.failed/.loaded through the loadPairedMacs flow including sign-out and team-switch resets. State transitions are correctly ordered.
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/DeletedComputerRecoveryButton.swift New shared component. isRecoveryInProgress correctly ORs store-level isRecovering with the view-local recoveryTask so the button stays disabled through the post-failure reload phase even after the store's defer fires.
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/DisconnectedWorkspaceShellView.swift Added recovery button to empty state and saved-computers list, gated shouldAutoPresentAddDeviceAfterLoadingSavedMacs behind pairedMacLoadState == .loaded, and extracted reloadAfterFailedDeletedComputerRecovery(). Logic is correct.
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/DeviceTreeView.swift Removed local isRecoveringDeletedComputer/@State tracking and replaced with shared DeletedComputerRecoveryButton; .onDisappear cleanup is now owned by the button component. Clean refactor with no regression.
Packages/iOS/CmuxMobileShell/Tests/CmuxMobileShellTests/IrohZeroTouchDiscoveryTests.swift Updated assertions for typed result enum and added three new tests covering alreadyInProgress, staleScope-on-signout, and staleScope-on-team-switch.
Packages/iOS/CmuxMobileShellUI/Tests/CmuxMobileShellUITests/DisconnectedWorkspaceShellRecoveryTests.swift New test suite covering showsDeletedComputerRecoveryAction, shouldAutoPresentAddDeviceAfterLoadingSavedMacs suppression, load-state gating, and failed-load behaviour. The prior ordering bug is correctly fixed here.

Sequence Diagram

sequenceDiagram
    participant UI as DeletedComputerRecoveryButton
    participant Store as MobileShellComposite
    participant Discovery as PersonalIrohDiscovery

    UI->>UI: recoverDeletedComputer()
    UI->>UI: "recoveryTask = Task"
    UI->>Store: await recover()
    Store->>Store: guard not isRecoveringDeletedComputer
    Store->>Store: "isRecoveringDeletedComputer = true"
    Store->>Store: currentScopeSnapshot() / forgottenMacDeviceIDs()
    Store->>Discovery: discoverLiveMacs()
    Discovery-->>Store: candidates
    Store->>Store: isScopeCurrent?

    alt scope stale
        Store-->>UI: .staleScope
        Store->>Store: "defer isRecoveringDeletedComputer = false"
    else candidate found
        Store->>Store: connectAccountDiscoveredIrohMac
        Store->>Store: loadPairedMacs + loadRegistryDevices
        Store-->>UI: .recovered
        Store->>Store: "defer isRecoveringDeletedComputer = false"
    else no candidate
        Store-->>UI: .notFound
        Store->>Store: "defer isRecoveringDeletedComputer = false"
        UI->>Store: await reloadAfterFailure
        Note over UI: recoveryTask keeps button disabled
        UI->>UI: show failure alert
        UI->>UI: "defer recoveryTask = nil"
    end
Loading

Reviews (5): Last reviewed commit: "Keep recovery button busy through reload" | Re-trigger Greptile

Comment on lines +20 to +28
@Test func recoverableDeletedComputerSuppressesAutomaticAddComputerSheet() async {
let store = await shellStore()
store.hasRecoverableDeletedComputers = true
await store.loadPairedMacs()

let view = disconnectedView(store: store)

#expect(!view.shouldAutoPresentAddDeviceAfterLoadingSavedMacs)
}

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.

P1 Test will always fail: loadPairedMacs() resets the flag it relies on

The test sets hasRecoverableDeletedComputers = true, then calls await store.loadPairedMacs(). Inside loadPairedMacs(), hasForgottenMacs is derived from forgottenMacDeviceIDs(scope:), which reads from the forgottenMacStore. The store is constructed here with the default InMemoryPairedMacForgottenStore() — empty — so hasForgottenMacs == false, and loadPairedMacs() overwrites hasRecoverableDeletedComputers = false before the assertion. view.shouldAutoPresentAddDeviceAfterLoadingSavedMacs then evaluates to true and the #expect(!...) fails. Moving store.hasRecoverableDeletedComputers = true to after the loadPairedMacs() call, or seeding the forgottenMacStore with a forgotten mac ID, fixes the ordering.

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!

Comment on lines +8 to +11
L10n.string(
"mobile.computers.recoveringDeleted",
defaultValue: "Recovering Deleted Computer..."
)

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 The defaultValue fallback for mobile.computers.recoveringDeleted uses three ASCII periods (...) while the string catalog entry and the code it replaced both use the Unicode horizontal ellipsis (…, U+2026). If the key is ever absent from the catalog — e.g., during localisation QA — the fallback renders a visually different string.

Suggested change
L10n.string(
"mobile.computers.recoveringDeleted",
defaultValue: "Recovering Deleted Computer..."
)
L10n.string(
"mobile.computers.recoveringDeleted",
defaultValue: "Recovering Deleted Computer…"
)

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

40-42: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Do not fall back to the shared .standard defaults suite.

If UserDefaults(suiteName:) returns nil, this helper silently shares application defaults with unrelated tests, making results order-dependent. Fail fixture setup instead of using .standard.

As per path instructions, test-backed UserDefaults state must be isolated per test.

🤖 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/DisconnectedWorkspaceShellRecoveryTests.swift`
around lines 40 - 42, Update the shellStore() test fixture to require the
uniquely named UserDefaults suite and fail setup when UserDefaults(suiteName:)
returns nil; remove the fallback to UserDefaults.standard so test state remains
isolated.

Source: Path instructions

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

Inline comments:
In
`@Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite.swift`:
- Line 2384: Guard the hasRecoverableDeletedComputers mutation in the loadAll
error path with isScopeCurrent(scope) before clearing it. Ensure a failed
request from an outdated account or team cannot mutate shared UI state, while
preserving the existing behavior for the current scope.

In
`@Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite`+ForgottenMacRecovery.swift:
- Around line 16-18: Update the recovery flow surrounding
isRecoveringDeletedComputer so an already-active attempt produces a distinct
“already in progress” outcome instead of false. Adjust the recovery method’s
result type and DeletedComputerRecoveryButton handling to preserve false
exclusively for actual recovery failures, while keeping the existing success and
in-progress behaviors explicit.

In
`@Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/DeletedComputerRecoveryButton.swift`:
- Around line 59-69: Update recoverDeletedComputer so the recovery operation has
explicit lifecycle ownership: store it in the shared store or a cancellable task
owned by the view, cancel it when the view disappears, and prevent
reloadAfterFailure or alertMessage updates after cancellation or deallocation.
Preserve the existing recovery and failure-message behavior while ensuring no
untracked Task remains active beyond the caller’s lifecycle.

---

Outside diff comments:
In
`@Packages/iOS/CmuxMobileShellUI/Tests/CmuxMobileShellUITests/DisconnectedWorkspaceShellRecoveryTests.swift`:
- Around line 40-42: Update the shellStore() test fixture to require the
uniquely named UserDefaults suite and fail setup when UserDefaults(suiteName:)
returns nil; remove the fallback to UserDefaults.standard so test state remains
isolated.
🪄 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 Plus

Run ID: c76a47db-47a4-4431-9563-05566946e1b4

📥 Commits

Reviewing files that changed from the base of the PR and between 952cced and 5731452.

📒 Files selected for processing (6)
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+ForgottenMacRecovery.swift
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/DeletedComputerRecoveryButton.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/DeviceTreeView.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/DisconnectedWorkspaceShellView.swift
  • Packages/iOS/CmuxMobileShellUI/Tests/CmuxMobileShellUITests/DisconnectedWorkspaceShellRecoveryTests.swift

Comment thread Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite.swift Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 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
`@Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite.swift`:
- Around line 2384-2386: Update the load outcome handling in the loadAll flow so
a failed paired-Mac load is explicitly recorded as unavailable or failed rather
than clearing hasRecoverableDeletedComputers. On the same store, track whether
paired Macs were successfully loaded, and update
DisconnectedWorkspaceShellView.shouldAutoPresentAddDeviceAfterLoadingSavedMacs
to auto-present only after a successful known-empty load, failing closed when
loading fails or remains unresolved.

In
`@Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite`+ForgottenMacRecovery.swift:
- Around line 5-13: Add a distinct stale/cancelled case to
MobileDeletedComputerRecoveryResult and return it whenever the captured
account/team scope is no longer current during recovery, instead of returning
.notFound. Update DeletedComputerRecoveryButton to ignore this outcome without
triggering reload or the “No deleted computer was recovered” alert, while
preserving existing handling for genuine .notFound results.

In
`@Packages/iOS/CmuxMobileShell/Tests/CmuxMobileShellTests/IrohZeroTouchDiscoveryTests.swift`:
- Line 101: Add a concurrent recovery test in CmuxMobileShellTests covering
recoverForgottenIrohMacFromAccount: suspend the first discovery attempt, start
recovery, invoke a second recovery, assert the second returns
.alreadyInProgress, then resume the suspended discovery and await the first
attempt.
🪄 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 Plus

Run ID: 355580d7-db41-431b-ab5e-fad2824f2dc8

📥 Commits

Reviewing files that changed from the base of the PR and between 5731452 and 8f6480b.

📒 Files selected for processing (5)
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+ForgottenMacRecovery.swift
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite.swift
  • Packages/iOS/CmuxMobileShell/Tests/CmuxMobileShellTests/IrohZeroTouchDiscoveryTests.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/DeletedComputerRecoveryButton.swift
  • Packages/iOS/CmuxMobileShellUI/Tests/CmuxMobileShellUITests/DisconnectedWorkspaceShellRecoveryTests.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
`@Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite`+ForgottenMacRecovery.swift:
- Line 65: Update the continuation predicate used by
connectAccountDiscoveredIrohMac in the forgotten-Mac recovery flow so it
validates the complete captured scope, including the team, rather than only the
user. Ensure persistence is skipped and .staleScope is returned when the team
changes during connection, and add a test covering that team-switch timing.
🪄 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 Plus

Run ID: 1ec027f8-8c6e-4d2f-825c-18d5410fa7a1

📥 Commits

Reviewing files that changed from the base of the PR and between 8f6480b and d1f96dc.

📒 Files selected for processing (6)
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+ForgottenMacRecovery.swift
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite.swift
  • Packages/iOS/CmuxMobileShell/Tests/CmuxMobileShellTests/IrohZeroTouchDiscoveryTests.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/DeletedComputerRecoveryButton.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/DisconnectedWorkspaceShellView.swift
  • Packages/iOS/CmuxMobileShellUI/Tests/CmuxMobileShellUITests/DisconnectedWorkspaceShellRecoveryTests.swift

}
}
}
.disabled(isRecovering)

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.

P1 Button appears enabled but is unresponsive during post-failure reload

When recover() returns .notFound, the store's defer { isRecoveringDeletedComputer = false } fires immediately, so isRecovering drops to false and the button re-enables. But recoveryTask is still live running reloadAfterFailure(). Any tap during that reload hits guard !isRecovering, recoveryTask == nil → silently returns. The old DeviceTreeView code kept its local isRecoveringDeletedComputer flag true through the full reload cycle; the refactor removed that invariant. The label also stays on the idle title during the reload rather than showing the in-progress text.

Suggested change
.disabled(isRecovering)
.disabled(isRecovering || recoveryTask != nil)

@cursor

cursor Bot commented Jul 23, 2026

Copy link
Copy Markdown

Bugbot is paused — on-demand spend limit reached

Bugbot uses usage-based billing for this team and has hit its on-demand spend limit.

A team admin can raise the spend limit in the Cursor dashboard, or wait for the next billing cycle to continue.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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+ForgottenMacRecovery.swift (1)

35-40: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Revalidate the captured scope before mutating shared recovery state and reporting success.

There are two async gaps where the captured scope can become stale:

  • After forgottenMacDeviceIDs(scope:), the stale task can cancel the new scope’s recovery owner via connectionRecoveryOwner.cancel() and invalidateStoredMacReconnectAttempt().
  • loadPairedMacs() and loadRegistryDevices() can suspend, but the method then returns .recovered without checking whether the account/team scope changed.

Add scope checks before the shared-state mutations and after each reload; also cover both timing windows with regression tests.

Proposed fix
         let forgottenIDs = await forgottenMacDeviceIDs(scope: scope)
+        guard await isScopeCurrent(scope) else { return .staleScope }
         guard !forgottenIDs.isEmpty else { return .notFound }

         connectionRecoveryOwner.cancel()
         applyConnectionRecoveryOwnerState()
         invalidateStoredMacReconnectAttempt()
...
             await loadPairedMacs()
+            guard await isScopeCurrent(scope) else { return .staleScope }
             await loadRegistryDevices()
+            guard await isScopeCurrent(scope) else { return .staleScope }
             return .recovered

As per path instructions, correctness-critical recovery state must use one authoritative scope and fail closed when that scope changes.

Also applies to: 66-70

🤖 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`+ForgottenMacRecovery.swift
around lines 35 - 40, The forgotten-Mac recovery flow must revalidate its
captured scope after forgottenMacDeviceIDs(scope:) before mutating shared
recovery state, and after each loadPairedMacs() and loadRegistryDevices()
suspension before returning .recovered. Use the authoritative current-scope
check to fail closed when the scope changes, preventing stale tasks from
cancelling or invalidating the new recovery owner; add regression tests covering
both timing windows.

Source: Path instructions

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

Outside diff comments:
In
`@Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite`+ForgottenMacRecovery.swift:
- Around line 35-40: The forgotten-Mac recovery flow must revalidate its
captured scope after forgottenMacDeviceIDs(scope:) before mutating shared
recovery state, and after each loadPairedMacs() and loadRegistryDevices()
suspension before returning .recovered. Use the authoritative current-scope
check to fail closed when the scope changes, preventing stale tasks from
cancelling or invalidating the new recovery owner; add regression tests covering
both timing windows.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 0de695ff-88ab-4051-af91-4f38de531f45

📥 Commits

Reviewing files that changed from the base of the PR and between d1f96dc and 1fbb2a9.

📒 Files selected for processing (2)
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+ForgottenMacRecovery.swift
  • Packages/iOS/CmuxMobileShell/Tests/CmuxMobileShellTests/IrohZeroTouchDiscoveryTests.swift

@azooz2003-bit
azooz2003-bit merged commit 7652d3b into main Jul 23, 2026
6 of 7 checks passed
@azooz2003-bit
azooz2003-bit deleted the fix-ios-recover-empty-state branch July 23, 2026 03:27
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