Skip to content

Mobile Connection Opt#254

Merged
arul28 merged 2 commits into
mainfrom
ade/mobile-connection-opt-eebafbf3
May 6, 2026
Merged

Mobile Connection Opt#254
arul28 merged 2 commits into
mainfrom
ade/mobile-connection-opt-eebafbf3

Conversation

@arul28
Copy link
Copy Markdown
Owner

@arul28 arul28 commented May 6, 2026

Summary

Describe the change.

What Changed

Key files and behaviors.

Validation

How you tested.

Risks

Anything to watch.

Summary by CodeRabbit

  • New Features

    • Auto-update system now tracks versions and improves installation flow
    • Connection health monitoring provides better visibility into device connectivity
    • Lane deletion displays progress tracking with visual feedback
  • Improvements

    • Enhanced iOS simulator launch target discovery and build error messages
    • Better connection status indicators throughout the app
    • Safer lane deletion with improved cleanup handling
  • Documentation

    • Updated CLI guidance and help text

Greptile Summary

This PR improves mobile connection health visibility by introducing a structured SyncConnectionHealth model on iOS, adds live delete-progress tracking for lane deletions with pre-navigation UX, upgrades the iOS Simulator target discovery to use xcscheme matching, and hardens the auto-update install flow with a concurrent-install guard and pre-install version refresh.

  • iOS connection health: Adds SyncConnectionHealth / SyncTransportHealth / SyncLoadHealth types and a pure syncConnectionHealth() function. A ConnectionHealthPresentation struct consolidates previously duplicated switch/computed-var blocks across ADEConnectionDot and ADERootToolbarControls, and surfaces strained-load and syncing states in accessibility labels.
  • Lane deletion UX: LanesPage now shows per-lane progress overlays (deleteProgressByLaneId) and navigates away from deleting lanes before the async delete call. laneService wraps database cleanup in an explicit BEGIN IMMEDIATE transaction and converts sync rmSync calls to async fs.promises.rm.
  • Auto-update hardening: Adds quitAndInstallPromise to collapse concurrent install calls, compareUpdateVersions to ignore superseded downloads, and refreshReadyUpdateBeforeInstall to pull the latest build before quitting.

Confidence Score: 5/5

Safe to merge — all changed paths are defensive, the concurrent-install race from the previous review is properly closed, and the iOS health model refactor is logically sound.

The three main change areas (iOS connection health, lane delete UX, auto-update hardening) are well-scoped and guarded. The concurrent-install race is closed via quitAndInstallPromise. The iOS SyncConnectionHealth refactor consolidates logic without changing observable behaviour. The single note raised (onError swallowing during readyRefreshInProgress) is a minor observability concern, not a correctness failure.

No files require special attention; the auto-update service is the most complex changed path and warrants a staging sanity-check of the readyRefreshInProgress / onError interaction.

Important Files Changed

Filename Overview
apps/desktop/src/main/services/updates/autoUpdateService.ts Adds concurrent install guard (quitAndInstallPromise), version comparison, ignoredDownloadVersion filtering, preservedOrIdlePatch refactor, and refreshReadyUpdateBeforeInstall logic. Addresses the previously flagged concurrent quitAndInstall race.
apps/ios/ADE/Services/SyncService.swift Introduces SyncConnectionHealth / SyncTransportHealth / SyncLoadHealth types, a pure syncConnectionHealth() function, and request-timeout reconnect suppression via lastInboundMessageAt.
apps/ios/ADE/Views/Components/ADEDesignSystem.swift Extracts connection presentation logic into ConnectionHealthPresentation struct, eliminating duplicated switch/computed-var blocks across ADEConnectionDot and ADERootToolbarControls.
apps/ios/ADE/Views/Settings/SettingsConnectionHeader.swift Migrates SettingsConnectionHeader and SettingsStatusDot from RemoteConnectionState to SyncConnectionHealth. isActiveState now only pulses on .connecting, which is intentional.
apps/desktop/src/main/services/lanes/laneService.ts Wraps database_cleanup in BEGIN IMMEDIATE / COMMIT / ROLLBACK transaction, converts sync rmSync calls to async fs.promises.rm, adds setImmediate yield for UI broadcast.
apps/desktop/src/renderer/components/lanes/LanesPage.tsx Adds live delete-progress tracking via deleteProgressByLaneId, moves navigation before the async delete call, and uses ref-captured closures to avoid IPC re-subscription churn.
apps/desktop/src/main/services/ios/iosSimulatorService.ts Replaces ad-hoc .xcodeproj discovery with discoverXcodeProjectPaths, parses xcscheme files to map schemes to PBXNativeTargets, adds productName/appTargetId, and provides stale-target recovery.
apps/desktop/src/renderer/components/app/AutoUpdateControl.tsx Adds installRequested local state for optimistic installing visual, replaces green ready chip with pulsing fuchsia, and cleans up copy.

Sequence Diagram

sequenceDiagram
    participant UI as AutoUpdateControl (Renderer)
    participant IPC as IPC / quitAndInstall
    participant Svc as autoUpdateService
    participant Upd as electron-updater

    UI->>IPC: updateQuitAndInstall()
    IPC->>Svc: quitAndInstall()
    Note over Svc: quitAndInstallPromise guard
    Svc->>Svc: refreshReadyUpdateBeforeInstall()
    Svc->>Upd: checkForUpdates() + await downloadPromise
    Upd-->>Svc: update-available / update-downloaded / error
    Note over Svc: ignoredDownloadVersion filters older downloads
    Svc-->>Svc: readyRefreshError check
    alt refresh OK
        Svc->>Svc: patchSnapshot(installing)
        Svc->>Upd: quitAndInstall(false, true)
        Upd-->>UI: app restarts
    else refresh failed
        Svc->>Svc: patchSnapshot(error)
        Svc-->>IPC: return false
        IPC-->>UI: setInstallRequested(false)
    end
Loading

Comments Outside Diff (3)

  1. apps/desktop/src/main/services/updates/autoUpdateService.ts, line 521-558 (link)

    P2 refreshReadyUpdateBeforeInstall loses the original ready update on cancelled download

    If checkForUpdates emits a newer update-available but the subsequent download is cancelled (e.g. network drops), onUpdateCancelled sets status to "idle" and refreshReadyUpdateBeforeInstall returns false, aborting the install. The user's original ready update is also lost because onUpdateCancelled cleans the cache. Worth considering whether cancellation during a pre-install refresh should revert to the previous ready state rather than aborting entirely.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: apps/desktop/src/main/services/updates/autoUpdateService.ts
    Line: 521-558
    
    Comment:
    **`refreshReadyUpdateBeforeInstall` loses the original ready update on cancelled download**
    
    If `checkForUpdates` emits a newer `update-available` but the subsequent download is cancelled (e.g. network drops), `onUpdateCancelled` sets status to `"idle"` and `refreshReadyUpdateBeforeInstall` returns `false`, aborting the install. The user's original ready update is also lost because `onUpdateCancelled` cleans the cache. Worth considering whether cancellation during a pre-install refresh should revert to the previous ready state rather than aborting entirely.
    
    How can I resolve this? If you propose a fix, please make it concise.

    Fix in Claude Code

  2. apps/desktop/src/renderer/components/lanes/LanesPage.tsx, line 2258-2296 (link)

    P2 Failed delete removes the overlay but leaves the user on a different lane

    When window.ade.lanes.delete(args) throws, the lane's entry is removed from deleteProgressByLaneId so the tab reverts to a normal appearance. But moveAwayFromDeletingLanes already navigated away and changed selection. The user ends up on a different lane while the failed lane's tab reappears with no obvious visual cue that it was the source of the error shown in the toolbar chip. Consider leaving the progress entry in the map (perhaps with a "failed" overallStatus) so the tab can render an error state, or scrolling the tab list to reveal the failed lane.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: apps/desktop/src/renderer/components/lanes/LanesPage.tsx
    Line: 2258-2296
    
    Comment:
    **Failed delete removes the overlay but leaves the user on a different lane**
    
    When `window.ade.lanes.delete(args)` throws, the lane's entry is removed from `deleteProgressByLaneId` so the tab reverts to a normal appearance. But `moveAwayFromDeletingLanes` already navigated away and changed selection. The user ends up on a different lane while the failed lane's tab reappears with no obvious visual cue that it was the source of the error shown in the toolbar chip. Consider leaving the progress entry in the map (perhaps with a `"failed"` overallStatus) so the tab can render an error state, or scrolling the tab list to reveal the failed lane.
    
    How can I resolve this? If you propose a fix, please make it concise.

    Fix in Claude Code

  3. apps/ios/ADE/Services/SyncService.swift, line 2800-2805 (link)

    P2 load == .strained reflects user preference, not actual host latency

    syncConnectionHealth sets load = .strained whenever prefersReducedSyncLoad == true and the transport is connected — but prefersReducedSyncLoad appears to be a user-facing preference, not a server-side latency signal. The ADEConnectionDot and ADERootToolbarControls then surface .strained as "Host is responding slowly" in accessibility labels, which would be incorrect when the host is fast and the user simply enabled reduced-load mode. Consider using a separate signal derived from request timeouts or server-side metrics to drive the .strained path.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: apps/ios/ADE/Services/SyncService.swift
    Line: 2800-2805
    
    Comment:
    **`load == .strained` reflects user preference, not actual host latency**
    
    `syncConnectionHealth` sets `load = .strained` whenever `prefersReducedSyncLoad == true` and the transport is connected — but `prefersReducedSyncLoad` appears to be a user-facing preference, not a server-side latency signal. The `ADEConnectionDot` and `ADERootToolbarControls` then surface `.strained` as "Host is responding slowly" in accessibility labels, which would be incorrect when the host is fast and the user simply enabled reduced-load mode. Consider using a separate signal derived from request timeouts or server-side metrics to drive the `.strained` path.
    
    How can I resolve this? If you propose a fix, please make it concise.

    Fix in Claude Code

Fix All in Claude Code

Prompt To Fix All With AI
Fix the following 1 code review issue. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 1
apps/desktop/src/main/services/updates/autoUpdateService.ts:374-385
**`readyRefreshInProgress` silently swallows unrelated updater errors**

When `readyRefreshInProgress` is `true`, the `onError` handler captures the error in `readyRefreshError` and returns early — regardless of whether the error originated from the refresh check or from an unrelated background updater event (e.g., a stale download emission). This means any updater error that races with the refresh window will abort the install silently, and if it was unrelated to the refresh, the user will see no feedback in the UI at all. The guard correctly prevents duplicate snapshot corruption, but it would be safer to also emit a warning log so operators can diagnose spurious install aborts in production.

Reviews (3): Last reviewed commit: "ship: iter 1 — address coderabbit + code..." | Re-trigger Greptile

@vercel
Copy link
Copy Markdown

vercel Bot commented May 6, 2026

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

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
ade Ignored Ignored Preview May 6, 2026 7:38am

@arul28
Copy link
Copy Markdown
Owner Author

arul28 commented May 6, 2026

@codex review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 6, 2026

📝 Walkthrough

Walkthrough

This PR substantially rewrites iOS simulator launch target discovery via robust Xcode PBX parsing, introduces comprehensive auto-update state management with Promise-based flow control, adds per-lane deletion progress tracking and transactional lane cleanup, and replaces direct connection-state checks with a new health-based model across iOS sync and UI layers. Four independent architectural improvements span desktop and iOS platforms.

Changes

iOS Simulator Launch Target Discovery

Layer / File(s) Summary
Data Shape & PBX Parsing
apps/ade-cli/README.md, apps/desktop/src/main/services/ios/iosSimulatorService.ts
Introduces PbxApplicationTarget, XcodeSchemeDefinition, and expanded ResolvedLaunchTarget with shouldBuild and shouldInstall fields. Adds helpers unquotePbxValue, readPbxAssignment, normalizePbxName, parseXmlAttributes.
Project & Scheme Discovery
apps/desktop/src/main/services/ios/iosSimulatorService.ts
New discoverXcodeProjectPaths enumerates root and apps-directory Xcode projects. parseApplicationTargetsFromPbxproj and schemesForApplicationTarget map targets to buildable schemes. Replaces ad-hoc app discovery with unified enumeration.
Launch Target Recovery
apps/desktop/src/main/services/ios/iosSimulatorService.ts
Added decodeTargetId and recoverStaleLaunchTarget to resolve stale IDs when projects change. Enhanced resolveLaunchTarget with fallback recovery logic.
Build Error Handling
apps/desktop/src/main/services/ios/iosSimulatorService.ts
New formatXcodeBuildFailure wraps xcodebuild failures with project, scheme, and device context. Updated error text for no-launchable-app scenarios.
Tests & Documentation
apps/desktop/src/main/services/ios/iosSimulatorService.test.ts, apps/desktop/src/shared/adeCliGuidance.ts
Helper writeMinimalXcodeProject scaffolds test Xcode structures. New test suites for root-level project discovery and Simulator.app launch visibility, including stale-ID recovery and error propagation. Guidance updated to discourage workarounds and recommend re-listing targets.

Auto-Update Flow & Quit-Install Control

Layer / File(s) Summary
Version Comparison & State Helpers
apps/desktop/src/main/services/updates/autoUpdateService.ts
Added parseVersion, comparePrereleaseIdentifier, compareUpdateVersions for robust version ordering. New UpdateCheckResultLike type, isUpdateCheckResultLike guard, and extractDownloadPromise helper. Internal state fields ignoredDownloadVersion, readyRefreshInProgress, readyRefreshError.
Update Lifecycle & Download Flow
apps/desktop/src/main/services/updates/autoUpdateService.ts
New runUpdateCheck orchestrator with internal guarding. Enhanced onDownloadProgress, onUpdateDownloaded, onUpdateNotAvailable, onError handlers with ignored-version checks and older-than-ready logic. Added refreshReadyUpdateBeforeInstall for pre-install verification.
Quit-Install Promise API
apps/desktop/src/main/services/updates/autoUpdateService.ts, apps/desktop/src/main/services/ipc/registerIpc.ts, apps/desktop/src/preload/global.d.ts, apps/desktop/src/preload/preload.ts
Changed quitAndInstall() from void to Promise<boolean>. IPC handler returns result via autoUpdateService.quitAndInstall() ?? false. Preload exposes typed signature. Integration points patch global state for install flow and error recovery.
Auto-Update CLI Surface
apps/ade-cli/src/cli.ts
New buildUpdatePlan router for update subcommands (actions, status/state/snapshot/show, check/check-for-updates, install/quit-and-install/apply, dismiss). Top-level aliases "auto-update" and "updates" map to "update". Expanded help text with Auto-update section and lifecycle documentation.
Tests & Mock Updates
apps/desktop/src/main/services/updates/autoUpdateService.test.ts, apps/desktop/src/renderer/browserMock.ts
New export of compareUpdateVersions. Enhanced test coverage with async patterns: download-progress tracking, pre-install refresh validation, and async install-error rollback. Mock updateQuitAndInstall resolves to true instead of undefined.

Lane Deletion Progress & Transactional Cleanup

Layer / File(s) Summary
Delete Progress Type & Helpers
apps/desktop/src/shared/types/core.ts, apps/desktop/src/renderer/components/lanes/LanesPage.tsx
Added LaneDeleteProgress to shared types. New helpers: isLaneDeleteProgressActive, createPendingDeleteProgress, getLaneDeleteStatusLabel, and resolveLaneDeleteStartSelection to manage per-lane deletion state and safe selection recovery when lanes are deleting.
Delete Progress State & Filtering
apps/desktop/src/renderer/components/lanes/LanesPage.tsx
New deleteProgressByLaneId state tracks per-lane progress. Derived selectableFilteredLaneIds excludes deleting lanes from navigation and filtering logic. Updated activeLaneIds and pinnedLaneIds computation to respect deletion state; keyboard navigation uses selectable lanes.
Delete Event Handling & Errors
apps/desktop/src/renderer/components/lanes/LanesPage.tsx
onDeleteEvent now updates progress, handles failed/cancelled statuses with user-friendly error messages, and cleans up stale progress. New effect prunes progress map when lanes change. Error banner dismissal added to header area.
Async Cleanup & Database Transactions
apps/desktop/src/main/services/lanes/laneService.ts
Replaced sync fs.rmSync with async fs.promises.rm for worktree removal. Added micro-yield via setImmediate in delete step. New pack_dir_remove step with resilient error handling. Database cleanup refactored into single begin immediate transaction, improving atomicity and consistency of multi-step cleanup.
Delete Progress UI & Indicators
apps/desktop/src/renderer/components/lanes/LanesPage.tsx
Added delete-progress spinner and status label overlay on lanes under deletion. Tab styling reflects deleting state (cursor, opacity). Status badges (REBASED, PENDING, FAILED, CONFLICT) added. Pin/close controls gated behind not-deleting state. Updated icon imports to support new UI elements.
Tests
apps/desktop/src/renderer/components/lanes/LanesPage.test.ts
New test suite for resolveLaneDeleteStartSelection validates selection and pane-state updates when a lane starts deleting, and unchanged selection when a different split deletes.

iOS Connection Health Model

Layer / File(s) Summary
Health Types & Computation
apps/ios/ADE/Services/SyncService.swift
New types: SyncTransportHealth enum (disconnected, connecting, connected, unreachable), SyncLoadHealth enum (normal, strained), SyncConnectionHealth struct (transport, load, lastFailureMessage). New helper syncConnectionHealth computes health from connectionState, load preference, and last error.
Inbound Activity Tracking
apps/ios/ADE/Services/SyncService.swift
Added lastInboundMessageAt state to track recent socket activity. New syncShouldReconnectAfterRequestTimeout helper gates reconnects after request timeouts based on socket silence. Constant SyncSocketTiming.requestTimeoutReconnectSilenceSeconds = 12. Updated handleIncoming and teardownSocket to maintain activity state.
Health-Driven Connection Dot UI
apps/ios/ADE/Views/Components/ADEDesignSystem.swift
ADEConnectionDot refactored to derive tint, glow, and accessibility labels from syncService.connectionHealth instead of direct state. New logic handles transport cases (disconnected, connecting, connected, unreachable) and load-driven status. ADERootToolbarControls adds connectionHealth proxy and health-based connectionTint, connectionIsAlive, and detailed connectionAccessibilityLabel.
Health-Based Settings Connection UI
apps/ios/ADE/Views/Settings/SettingsConnectionHeader.swift, apps/ios/ADE/Views/Settings/SettingsSupportTypes.swift
SettingsConnectionHeader now uses health proxy and health-driven rendering for status display, host details, error messaging, and pulsing. SettingsConnectionPresentation methods (statusLabel, statusTint, glowTint) rewritten to accept SyncConnectionHealth instead of RemoteConnectionState; output reflects transport and load states with labels like "Connected, slow".
Content View Color Logic
apps/ios/ADE/App/ContentView.swift
Attached-computer indicator color now derives from syncService.connectionHealth instead of connectionState; uses health.transport with load-aware differentiation for connected state.
Tests
apps/ios/ADETests/ADETests.swift
New tests validate syncShouldReconnectAfterRequestTimeout silence-based gating, and syncConnectionHealth behavior across syncing (treated as connected), strained load, unreachable, and disconnected states. Verify correct transport/load/failure-message mappings.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~90 minutes


Possibly related PRs

  • arul28/ADE#229: Modifies iOS simulator helper discovery and iosSimulatorService runtime fallback logic, directly related to the iOS Simulator Launch Target Discovery cohort.
  • arul28/ADE#164: Reworks ADEConnectionDot/ADEDesignSystem and connection UI presentation, closely related to the iOS Connection Health Model cohort refactoring connection display logic.
  • arul28/ADE#216: Modifies iosSimulatorService.ts with attachToChatSession and background-launch adjustments, related to the iOS Simulator Discovery rewrite.

Suggested labels

desktop, ios

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 1.25% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Mobile Connection Opt' is vague and does not clearly convey the scope of changes. The PR contains substantial modifications across iOS sync/connection health, simulator service, auto-update service, lanes UI, and CLI features—the title does not summarize these meaningful changes. Consider using a more descriptive title such as 'Add connection health tracking and refactor update/simulator services' to better reflect the multi-layered changes across iOS sync, desktop services, and CLI.
✅ Passed checks (3 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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 ade/mobile-connection-opt-eebafbf3

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 and usage tips.

@chatgpt-codex-connector
Copy link
Copy Markdown

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

@capy-ai
Copy link
Copy Markdown

capy-ai Bot commented May 6, 2026

Capy auto-review is paused for this organization because the monthly auto-review limit has been reached. Increase the limit or turn it off in billing settings to resume automatic reviews.

Comment thread apps/desktop/src/main/services/updates/autoUpdateService.ts Outdated
Tighten iOS sync/connection settings, expand iOS simulator service with
tests, refactor auto-update service with broader test coverage, rework
the desktop Lanes page, and refresh related docs.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@arul28 arul28 force-pushed the ade/mobile-connection-opt-eebafbf3 branch from e20d167 to 5079548 Compare May 6, 2026 07:11
@arul28
Copy link
Copy Markdown
Owner Author

arul28 commented May 6, 2026

@codex review

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

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

⚠️ Outside diff range comments (1)
apps/ios/ADE/Services/SyncService.swift (1)

6088-6100: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Don't return the "Reconnecting now" timeout when this branch skips reconnect.

When recent inbound traffic suppresses the reconnect path here, the request still fails with SyncRequestTimeout.message, so the user is told ADE is reconnecting even though the socket stays up. Use a non-reconnect timeout message in this branch.

Suggested fix
     if disconnectOnTimeout,
        syncShouldReconnectAfterRequestTimeout(
          now: ProcessInfo.processInfo.systemUptime,
          lastInboundMessageAt: lastInboundMessageAt
        ) {
       // handleTransportFailure tears the socket down before flipping the
       // reduced-load preference, so we must NOT call markConnectionLoadStrained
       // here — calling it pre-teardown re-subscribes chat events on the
       // doomed socket. The transport-failure path strains the load itself.
       handleTransportFailure(timeoutError)
     } else {
+      let resolvedError =
+        disconnectOnTimeout
+        ? SyncRequestTimeout.error(message: "The host took too long to respond. Try again.")
+        : timeoutError
       markConnectionLoadStrained()
-      resolve(requestId: requestId, result: .failure(timeoutError))
+      resolve(requestId: requestId, result: .failure(resolvedError))
     }
🤖 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 `@apps/ios/ADE/Services/SyncService.swift` around lines 6088 - 6100, The else
branch currently resolves the request with the same reconnect-oriented timeout
error even when syncShouldReconnectAfterRequestTimeout(...) returns false;
change the error used in that branch so it signals a non-reconnect timeout.
Specifically, when disconnectOnTimeout is true but
syncShouldReconnectAfterRequestTimeout(...) is false, replace the reconnecting
timeoutError passed to resolve(requestId: requestId, result:
.failure(timeoutError)) with a distinct non-reconnect timeout error (e.g., a new
SyncRequestTimeout case or a different error instance/message) while keeping the
existing calls to markConnectionLoadStrained() and not calling
handleTransportFailure().
🧹 Nitpick comments (3)
apps/ios/ADE/Views/Components/ADEDesignSystem.swift (1)

453-521: ⚡ Quick win

Extract the connection-health presentation once.

This health-to-UI mapping is duplicated again at Lines 622-689. The two copies already have enough branching that the next state or accessibility tweak is likely to diverge between the standalone button and the root toolbar. A single private helper/view-model for tint, glow, and label text would make this safer to evolve.

🤖 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 `@apps/ios/ADE/Views/Components/ADEDesignSystem.swift` around lines 453 - 521,
Extract the duplicated health-to-UI logic into one small helper (e.g., a private
struct or view-model like ConnectionHealthPresentation or
makeConnectionPresentation()) that reads syncService and SyncConnectionHealth
and exposes the three properties used throughout the UI: tint (Color),
showsConnectedGlow (Bool) and accessibilityLabel (String); move the existing
computed logic from the private vars tint, showsConnectedGlow, truncatedHostName
and accessibilityLabel into that helper (keeping truncatedHostName as an
internal helper) and replace both copies in the file with a single
instantiation/usage of that helper (refer to the existing symbols health,
syncService, truncatedHostName, and the switch on health.transport to preserve
the exact branching and text).
apps/desktop/src/renderer/components/lanes/LanesPage.test.ts (1)

136-165: ⚡ Quick win

New resolveLaneDeleteStartSelection tests are correct

Both traces verify cleanly against the implementation:

  • Test 1 — deleting the selected+pinned lane ("lane-b"): isAvailable("lane-b") is false, so selection falls through to the first non-deleting entry in filteredLaneIds → "lane-c"; "lane-d" is preserved in both activeLaneIds and pinnedLaneIds. Assertions are exact.
  • Test 2 — deleting a non-selected split lane ("lane-c"): current selection "lane-b" is available, so nextSelectedLaneId stays; "lane-c" is dropped from activeLaneIds while "lane-d" is retained as a preserved active.

One untested path worth adding: all filteredLaneIds are deleting, forcing the fallback through sortedLaneIds. The current two cases don't exercise that branch.

🤖 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 `@apps/desktop/src/renderer/components/lanes/LanesPage.test.ts` around lines
136 - 165, Add a new test to cover the branch where every id in filteredLaneIds
is being deleted so the function falls back to sortedLaneIds; in the
LanesPage.test.ts suite add a case for resolveLaneDeleteStartSelection where
deletingLaneIds contains all filteredLaneIds (e.g.,
["lane-b","lane-c","lane-d"]), set selectedLaneId to a deleting entry, and
assert that nextSelectedLaneId and active/pinned behavior come from the first
non-deleting id in sortedLaneIds (using the same symbols
resolveLaneDeleteStartSelection, filteredLaneIds, sortedLaneIds,
deletingLaneIds, selectedLaneId, activeLaneIds, pinnedLaneIds).
apps/desktop/src/renderer/components/lanes/LanesPage.tsx (1)

786-837: ⚡ Quick win

onDeleteEvent re-subscribes on every lane selection — use refs for high-churn captured values

selectedLaneId (changes on every tab click) and lanesById (changes on every refreshLanes) are in the dependency array, so the IPC listener is torn down and re-added on each of those events. managedLaneIds and manageOpen add further churn. In practice this means a live deletion's progress events could be processed by a stale listener instance, and in aggregate these rebuilds are wasteful.

The fix is to capture the four high-churn values in refs so the handler always reads the live value without forcing a re-subscription:

♻️ Proposed refactor
+  const selectedLaneIdRef = useRef<string | null>(null);
+  useEffect(() => { selectedLaneIdRef.current = selectedLaneId; });
+  const lanesByIdRef = useRef(lanesById);
+  useEffect(() => { lanesByIdRef.current = lanesById; });
+  const managedLaneIdsRef = useRef(managedLaneIds);
+  useEffect(() => { managedLaneIdsRef.current = managedLaneIds; });
+  const manageOpenRef = useRef(manageOpen);
+  useEffect(() => { manageOpenRef.current = manageOpen; });

   useEffect(() => {
     const unsubscribe = window.ade.lanes.onDeleteEvent((event) => {
       const { laneId, overallStatus } = event.progress;
       setDeleteProgressByLaneId((prev) => { ... });
       if (overallStatus === "failed" || overallStatus === "cancelled") {
-        const laneName = lanesById.get(laneId)?.name ?? laneId;
+        const laneName = lanesByIdRef.current.get(laneId)?.name ?? laneId;
         ...
       }
       ...
-      if (selectedLaneId === laneId) selectLane(null);
+      if (selectedLaneIdRef.current === laneId) selectLane(null);
       ...
       void refreshLanes()
         .then(() => {
-          if (manageOpen && (selectedLaneId === laneId || managedLaneIds.includes(laneId))) {
+          if (manageOpenRef.current && (selectedLaneIdRef.current === laneId || managedLaneIdsRef.current.includes(laneId))) {
             setManageOpen(false);
           }
         })
     });
     return unsubscribe;
-  }, [clearLaneInspectorTab, lanesById, managedLaneIds, manageOpen, refreshLanes, selectLane, selectedLaneId]);
+  }, [clearLaneInspectorTab, refreshLanes, selectLane]);
🤖 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 `@apps/desktop/src/renderer/components/lanes/LanesPage.tsx` around lines 786 -
837, The useEffect subscribing to window.ade.lanes.onDeleteEvent re-subscribes
on every change to high-churn values (selectedLaneId, lanesById, managedLaneIds,
manageOpen); capture those live values in refs instead so the handler reads
current state without forcing re-subscription. Create refs (e.g.
selectedLaneIdRef, lanesByIdRef, managedLaneIdsRef, manageOpenRef), update them
whenever the corresponding state changes, then read from those refs inside the
onDeleteEvent handler; keep the useEffect dependency array minimal (include only
stable functions/refs like clearLaneInspectorTab, refreshLanes, selectLane and
completedLaneDeleteRefreshesRef) and remove the high-churn state vars from it.
Ensure existing calls that used those state variables (e.g. the logic around
selectLane(null), setActiveLaneIds, setPinnedLaneIds, setManagedLaneIds,
setLaneActionError, setDeleteProgressByLaneId, clearLaneInspectorTab,
refreshLanes and the completedLaneDeleteRefreshesRef checks) now reference the
ref values where needed inside the handler so behavior stays correct.
🤖 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 `@apps/desktop/src/main/services/ios/iosSimulatorService.ts`:
- Around line 2735-2745: The code currently keys project targets using
targetId(["project", relativeProject, scheme]) and omits app-specific
identifiers, causing collisions when a scheme can produce multiple app bundles;
update the target object created in the targets.push call to include the
app-specific discriminator (carry appTarget.id and
appTarget.productName/productName) and modify the ResolvedLaunchTarget shape to
store that productName/id, then update findAppBundle() to prefer the provided
productName/appTarget.id when resolving the .app (instead of assuming
`${scheme}.app`), ensuring project targets are distinguished by both scheme and
appTarget identity.

In `@apps/desktop/src/main/services/lanes/laneService.ts`:
- Around line 3234-3241: The pack_dir_remove step currently swallows
fs.promises.rm errors silently; update the catch to call logger.warn including
the lanePackDir and the caught error (reference pack_dir_remove, runStep,
lanePackDir, resolveAdeLayout, projectRoot, fs.promises.rm) so operators can
diagnose failures, and also return or surface a progress "detail" from the async
function (e.g., return a { detail: string } or otherwise set the step's progress
detail) describing the cleanup failure so the UI shows the state.

In `@apps/ios/ADE/App/ContentView.swift`:
- Around line 145-153: The switch over syncService.connectionHealth.transport
currently collapses .unreachable and .disconnected to ADEColor.textMuted; change
it so .unreachable returns the danger tint (e.g. ADEColor.danger or the app's
error color) while only .disconnected returns ADEColor.textMuted, keeping the
existing behavior for .connected and .connecting; update the case arms in the
switch on health.transport in ContentView.swift (the switch handling
connectionHealth) accordingly so "Connection error" is visually distinct from
"No computer attached."

In `@apps/ios/ADE/Views/Settings/SettingsConnectionHeader.swift`:
- Around line 301-310: The dotColor computed property currently returns a
saturated purple for the .disconnected branch, which makes a disconnected state
appear active; update the .disconnected case inside the dotColor switch (in the
dotColor computed var) to use a neutral/inactive tint (for example
ADEColor.inactive or a desaturated Color like Color.gray or Color with lower
saturation/opacity) so the status dot visually matches the "Not connected" state
shown elsewhere.

---

Outside diff comments:
In `@apps/ios/ADE/Services/SyncService.swift`:
- Around line 6088-6100: The else branch currently resolves the request with the
same reconnect-oriented timeout error even when
syncShouldReconnectAfterRequestTimeout(...) returns false; change the error used
in that branch so it signals a non-reconnect timeout. Specifically, when
disconnectOnTimeout is true but syncShouldReconnectAfterRequestTimeout(...) is
false, replace the reconnecting timeoutError passed to resolve(requestId:
requestId, result: .failure(timeoutError)) with a distinct non-reconnect timeout
error (e.g., a new SyncRequestTimeout case or a different error
instance/message) while keeping the existing calls to
markConnectionLoadStrained() and not calling handleTransportFailure().

---

Nitpick comments:
In `@apps/desktop/src/renderer/components/lanes/LanesPage.test.ts`:
- Around line 136-165: Add a new test to cover the branch where every id in
filteredLaneIds is being deleted so the function falls back to sortedLaneIds; in
the LanesPage.test.ts suite add a case for resolveLaneDeleteStartSelection where
deletingLaneIds contains all filteredLaneIds (e.g.,
["lane-b","lane-c","lane-d"]), set selectedLaneId to a deleting entry, and
assert that nextSelectedLaneId and active/pinned behavior come from the first
non-deleting id in sortedLaneIds (using the same symbols
resolveLaneDeleteStartSelection, filteredLaneIds, sortedLaneIds,
deletingLaneIds, selectedLaneId, activeLaneIds, pinnedLaneIds).

In `@apps/desktop/src/renderer/components/lanes/LanesPage.tsx`:
- Around line 786-837: The useEffect subscribing to
window.ade.lanes.onDeleteEvent re-subscribes on every change to high-churn
values (selectedLaneId, lanesById, managedLaneIds, manageOpen); capture those
live values in refs instead so the handler reads current state without forcing
re-subscription. Create refs (e.g. selectedLaneIdRef, lanesByIdRef,
managedLaneIdsRef, manageOpenRef), update them whenever the corresponding state
changes, then read from those refs inside the onDeleteEvent handler; keep the
useEffect dependency array minimal (include only stable functions/refs like
clearLaneInspectorTab, refreshLanes, selectLane and
completedLaneDeleteRefreshesRef) and remove the high-churn state vars from it.
Ensure existing calls that used those state variables (e.g. the logic around
selectLane(null), setActiveLaneIds, setPinnedLaneIds, setManagedLaneIds,
setLaneActionError, setDeleteProgressByLaneId, clearLaneInspectorTab,
refreshLanes and the completedLaneDeleteRefreshesRef checks) now reference the
ref values where needed inside the handler so behavior stays correct.

In `@apps/ios/ADE/Views/Components/ADEDesignSystem.swift`:
- Around line 453-521: Extract the duplicated health-to-UI logic into one small
helper (e.g., a private struct or view-model like ConnectionHealthPresentation
or makeConnectionPresentation()) that reads syncService and SyncConnectionHealth
and exposes the three properties used throughout the UI: tint (Color),
showsConnectedGlow (Bool) and accessibilityLabel (String); move the existing
computed logic from the private vars tint, showsConnectedGlow, truncatedHostName
and accessibilityLabel into that helper (keeping truncatedHostName as an
internal helper) and replace both copies in the file with a single
instantiation/usage of that helper (refer to the existing symbols health,
syncService, truncatedHostName, and the switch on health.transport to preserve
the exact branching and text).
🪄 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: CHILL

Plan: Pro

Run ID: 4d8f500b-4611-49e8-a616-6cdd72755920

📥 Commits

Reviewing files that changed from the base of the PR and between 80d395e and e20d167.

⛔ Files ignored due to path filters (5)
  • docs/ARCHITECTURE.md is excluded by !docs/**
  • docs/features/ios-simulator/README.md is excluded by !docs/**
  • docs/features/lanes/README.md is excluded by !docs/**
  • docs/features/onboarding-and-settings/README.md is excluded by !docs/**
  • docs/features/sync-and-multi-device/ios-companion.md is excluded by !docs/**
📒 Files selected for processing (23)
  • apps/ade-cli/README.md
  • apps/ade-cli/src/cli.ts
  • apps/desktop/src/main/services/ios/iosSimulatorService.test.ts
  • apps/desktop/src/main/services/ios/iosSimulatorService.ts
  • apps/desktop/src/main/services/ipc/registerIpc.ts
  • apps/desktop/src/main/services/lanes/laneService.ts
  • apps/desktop/src/main/services/updates/autoUpdateService.test.ts
  • apps/desktop/src/main/services/updates/autoUpdateService.ts
  • apps/desktop/src/preload/global.d.ts
  • apps/desktop/src/preload/preload.ts
  • apps/desktop/src/renderer/browserMock.ts
  • apps/desktop/src/renderer/components/app/AutoUpdateControl.tsx
  • apps/desktop/src/renderer/components/chat/ChatIosSimulatorPanel.tsx
  • apps/desktop/src/renderer/components/lanes/LanesPage.test.ts
  • apps/desktop/src/renderer/components/lanes/LanesPage.tsx
  • apps/desktop/src/shared/adeCliGuidance.ts
  • apps/desktop/src/shared/types/core.ts
  • apps/ios/ADE/App/ContentView.swift
  • apps/ios/ADE/Services/SyncService.swift
  • apps/ios/ADE/Views/Components/ADEDesignSystem.swift
  • apps/ios/ADE/Views/Settings/SettingsConnectionHeader.swift
  • apps/ios/ADE/Views/Settings/SettingsSupportTypes.swift
  • apps/ios/ADETests/ADETests.swift

Comment thread apps/desktop/src/main/services/ios/iosSimulatorService.ts
Comment thread apps/desktop/src/main/services/lanes/laneService.ts
Comment thread apps/ios/ADE/App/ContentView.swift
Comment thread apps/ios/ADE/Views/Settings/SettingsConnectionHeader.swift Outdated
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

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: 5079548e37

ℹ️ 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 +567 to +570
async quitAndInstall(): Promise<boolean> {
if (snapshot.status !== "ready" || !snapshot.version) return false;
const refreshSucceeded = await refreshReadyUpdateBeforeInstall();
if (!refreshSucceeded) return false;
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 Serialize quit-and-install requests in the update service

quitAndInstall() now awaits refreshReadyUpdateBeforeInstall(), but there is no service-level in-flight guard before that await. If two IPC callers invoke install while status is still ready, both calls can pass the initial check, both await the same refresh, and both proceed to call updater.quitAndInstall(false, true). This race is new in the async flow and can trigger duplicate install attempts or inconsistent updater state when multiple renderer actions (or automation callers) fire close together.

Useful? React with 👍 / 👎.

- iosSim: key project targets by scheme + appTarget identity (incl productName)
- lanes: log + surface pack_dir_remove cleanup failures
- autoUpdate: serialize quitAndInstall via module-level guard (matches checkPromise)
- iOS ContentView/SettingsConnectionHeader: distinguish .unreachable (danger) from .disconnected (muted); fix disconnected dot color
- iOS SyncService: use non-reconnect timeout message when reconnect path is skipped
- iOS ADEDesignSystem: extract ConnectionHealthPresentation, dedupe across button + toolbar
- LanesPage: add all-deleting fallback test; capture high-churn vars via refs to stop onDeleteEvent re-subscription churn

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@arul28
Copy link
Copy Markdown
Owner Author

arul28 commented May 6, 2026

@codex review

@chatgpt-codex-connector
Copy link
Copy Markdown

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

@arul28 arul28 merged commit d5c2212 into main May 6, 2026
24 checks passed
@arul28 arul28 deleted the ade/mobile-connection-opt-eebafbf3 branch May 6, 2026 07:53
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