Skip to content

Add comprehensive privacy-safe iOS app diagnostics - #9977

Merged
azooz2003-bit merged 26 commits into
mainfrom
feat-ios-comprehensive-logging
Aug 12, 2026
Merged

azooz2003-bit merged 26 commits into
mainfrom
feat-ios-comprehensive-logging

Conversation

@azooz2003-bit

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

Copy link
Copy Markdown
Collaborator

Summary

  • Add one append-only, privacy-safe iOS diagnostic vocabulary with 454 stable feature events and typed categorical details.
  • Record app lifecycle, authentication, onboarding, push, pairing, workspaces, terminal, browser, chat, artifacts, attachments, notifications, settings, analytics, persistence, simulator, and transport behavior through one injected diagnostic spine.
  • Persist readable app and network logs in Release builds. Each file rotates at 5 MB, the structured ring retains 4,096 events, repeated frame events coalesce, and raw user content or identifiers cannot enter the typed API.
  • Correlate related events with per-process handles, emit classified Sentry breadcrumbs, localize diagnostic presentation in English and Japanese, and expose both files from iOS Settings.
  • Prevent the tagged macOS host from crashing when SwiftUI sends a null child through the Objective-C view-mutation hook; AppKit still receives the original call, while Swift hierarchy tracking skips the absent view.

Testing

  • Focused Swift package tests passed for the diagnostic core and touched chat, telemetry, shell, shell-model, shell-UI, and toast paths.
  • Static producer audit: 454/454 event kinds have a producer, with 635 recordAppEvent call sites.
  • Privacy audit: 0 producer gaps, 0 obvious raw-error fields, and the isolated simulator logs contained no email, credential/token, terminal-content, command, prompt, query, or long secret-like value.
  • Package conventions lint passed in iOS simulator tests.
  • The original tagged macOS diagnostics build passed in Reload Build.
  • The launch-crash regression crashes at the null-subview mutation on the test-only commit, then executes exactly one test and passes on the fixed head.
  • The fixed ioslog macOS app passed the hosted tagged build, rendered on LG HDR 4K, kept its tagged socket alive, minted a trusted physical-device ticket, and produced no new crash report.
  • An Apple Silicon fleet Mac completed the iOS device archive, local signing/export completed, and the exact head launched in the isolated cmux-dev-ioslog iOS 27.0 simulator.
  • Runtime files were verified with build/session headers, app and network routing, launch/auth/onboarding/push/lifecycle events, a bounded diagnostic report, and prior-generation rotation files.
  • The unfiltered 1,018-test CmuxMobileShell job remains red on timing-sensitive failures also reproduced on origin/main. Both full simulator jobs reached the workflow's 35-minute limit. Focused coverage and the direct archive/build path are green.
  • The app remains installed on Aziz, but authenticated dogfood verification is blocked because CoreDevice reports the phone unavailable. The fixed Mac host now stays up and mints a trusted physical-device ticket; iPhone authentication and pairing remain unverified until the phone is reachable.

Demo Video

Checklist

  • Every diagnostic event has a producer.
  • Payloads are fixed enums, bounded integers, timings, counts, and process-local correlations.
  • Durable app/network logs are bounded, rotated, readable, and available in Release builds.
  • New user-facing text is localized in English and Japanese.
  • Focused tests, lint, device archive, signed export, simulator launch, and runtime log inspection passed.
  • Exercise both Settings share-sheet rows with uniquely targeted GUI automation.
  • Reproduce the macOS null-subview launch crash, land the Objective-C boundary guard, and prove the focused regression red then green.
  • Verify the physical-iPhone app is past login and paired; CoreDevice currently reports Aziz unavailable.

View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

@greptile-apps

greptile-apps Bot commented Aug 11, 2026 •

Copy link
Copy Markdown
Contributor

Too many files changed for review (134 files, 100 file limit).

Bypass the limit by tagging @greptile-apps to review.

@coderabbitai

coderabbitai Bot commented Aug 11, 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

The change adds privacy-safe diagnostic contracts and records structured events across mobile application, chat, browser, terminal, settings, attachment, notification, pairing, and lifecycle flows. It also wires the shared logger through application composition and adds validation tests.

Changes

Diagnostic telemetry

Layer / File(s) Summary
Event contracts, correlation, and presentation
Packages/Shared/CMUXMobileCore/...
Adds stable event taxonomies, typed payload enums, process-local correlation handles, bounded recording APIs, localized presentation, unknown-value handling, and failure categories.
Component event propagation
Packages/Shared/CmuxAgentChat/..., Packages/iOS/CmuxAgentChatUI/..., Packages/iOS/CmuxMobileBrowser/..., Packages/iOS/CmuxMobileSupport/..., Packages/iOS/CmuxMobileTerminal/..., Packages/iOS/CmuxMobileToast/...
Adds diagnostic callbacks and records chat, artifact, browser, dictation, terminal, attachment, toast, and analytics events.
Shell operation instrumentation
Packages/iOS/CmuxMobileShell/...
Records lifecycle, success, failure, timing, count, correlation, state-sync, workspace, browser-stream, terminal, artifact, pairing, notification, persistence, and attachment events.
Application wiring and validation
ios/..., ios/cmuxPackage/..., Packages/**/Tests/...
Injects the shared logger through application composition and validates routing, stable values, privacy redaction, settings, attachments, toast events, and telemetry classification.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

Suggested reviewers: lawrencecchen


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 Concurrency ❌ Error CMUXMobileRootView adds private startOpenURLConnection(_:onResult:), which wraps an async connect in an escaping result callback between cmux-owned callers and callee. Make the helper async or let its stored Task handle the result directly. Keep only the SwiftUI .onOpenURL closure as the framework boundary.
Cmux Swift @Concurrent ❌ Error Changed ChatComposerView.loadPickedItems runs from inherited UI Task, loads Data, then performs synchronous UIImage decode/downscale/JPEG work with no explicit hop or @concurrent boundary. Move PhotosPicker loading and image conversion into a Sendable @concurrent or detached helper, then return bounded results to the MainActor for state and diagnostics updates.
Cmux Architecture Rethink ❌ Error ChatComposerView splits staging state: cancel changes the owner generation, so the deferred cleanup skips clearing isStagingAttachments and Send can remain disabled. Make one staging owner hold busy state, generation, and task, or make cancel clear isStagingAttachments synchronously; test cancellation on disappearance and replacement.
Docstring Coverage ⚠️ Warning Docstring coverage is 38.92% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (21 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 Changed UI stores and coordinators use @MainActor, actor services own mutable state, DiagnosticCorrelation is nonisolated, and no new async service protocol or unisolated mutable Sendable type appe...
Cmux Swift Blocking Runtime ✅ Passed The production diff adds no semaphores, blocking waits, delayed dispatch, main-queue sync, manual locks, or executable polling loops; the existing ContinuousClock backoff expression is unchanged.
Cmux Browser Automation Off-Main ✅ Passed The PR does not change TerminalController, socketWorkerMethods, or policy tests; its browser changes are iOS UI/stream diagnostics, so no browser socket worker routing is added or worsened.
Cmux Expensive Synchronous Load ✅ Passed No new or moved RestorableAgentSessionIndex load, agent-history file read, directory scan, or large JSONL parse appears; chat history remains async RPC work and large diff decoding uses a nonisolat...
Cmux Cache Substitution Correctness ✅ Passed No fresh authoritative read was replaced. Cache changes only report artifact hits; PresenceMap.instanceCount is an in-memory diagnostic derivation, while persistence and chat-history reads remain i...
Cmux No Hacky Sleeps ✅ Passed The PR changes only three Swift files; the rule scopes out Swift and the full PR diff adds no TypeScript, JavaScript, shell, or build/runtime-script code or delays.
Cmux Algorithmic Complexity ✅ Passed The added scalable traversals are linear: PresenceMap maintains O(1) transition counts, attachment cleanup uses a Set, and composer/toast paths use explicit bounds (4 and 3).
Cmux Swift Package Boundaries ✅ Passed The production diff keeps 120 Swift files in SwiftPM packages; the only four ios/cmux app-target files are app composition, delegate, entry point, and UIKit lifecycle observation, which the rule al...
Cmux Swiftpm Lockfiles ✅ Passed The PR changes only ios/cmux-ios.xcodeproj source membership; no Package.swift, package-local Package.resolved, .gitignore, workflow, or SwiftPM package-reference changes appear.
Cmux Swift Logging ✅ Passed The diff adds no production print/debugPrint/dump/NSLog or ad hoc I/O; MobileDebugLog calls route through #if DEBUG, and changed Logger values use .private redaction.
Cmux User-Facing Error Privacy ✅ Passed Changed UI copy is generic diagnostic text; Error values are reduced to DiagnosticFailureKind, and newly added raw-error logs use private privacy interpolation.
Cmux Full Internationalization ✅ Passed New diagnostic copy uses localized/L10n APIs; all added keys have translated en and ja entries in each touched catalog, and no web localization files changed.
Cmux Swiftui State Layout ✅ Passed The PR adds no GeometryReader, lazy/list boundary, ObservableObject, @Published, or legacy observation pattern; notification rows retain snapshots/actions, and new state writes occur in lifecycle o...
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed The PR adds no standalone window code or close-shortcut changes; added diff lines contain no NSWindow/NSPanel/WindowGroup APIs, and scripts/lint_auxiliary_window_close_shortcuts.py passes.
Cmux Source Artifacts ✅ Passed The 158-path cumulative diff contains source, tests, localization catalogs, project configs, and docs; no prohibited scratch, cache, build, log, screenshot, recording, or dependency artifact paths...
Cmux No Test Or Debug Seam In Production Source ✅ Passed The production diff adds no test/debug accessors, test-build guards, or test-named members; changed DEBUG blocks gate real diagnostic behavior, and existing seams are untouched.
Cmux No Ambient Global State ✅ Passed The full PR diff adds no file-scope mutable vars or free functions. New state is held by constructable injected types; static members are constants, enum cases, or required EnvironmentKey defaults.
Title check ✅ Passed The title clearly and concisely summarizes the primary change: adding comprehensive privacy-safe iOS diagnostics.
Description check ✅ Passed The description thoroughly covers the changes, testing, demo, and checklist, but omits the template's Review Trigger section and standard review checklist items.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat-ios-comprehensive-logging

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.

Comment thread Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileChatEventSource.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: 29

Caution

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

⚠️ Outside diff range comments (4)
Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+NotificationFeed.swift (1)

160-208: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Report one affected item for both read-state mutations.

Lines 165, 192, and 207 report count: 0 when marking an item unread. The operation still affects one item. The combined guard also reports endpointUnavailable when item.isRead == isRead, although that path is a no-op.

Use count: 1 for either mutation. Emit no event, or a distinct no-op event, when the requested state already matches the item state.

🤖 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`+NotificationFeed.swift
around lines 160 - 208, Update the notification read-state mutation flow around
the target guard and all notificationFeedItemMarkedRead recordAppEvent calls to
skip event reporting when item.isRead already equals isRead, while preserving
the no-op behavior. For actual read or unread mutations, always report count: 1,
including endpoint-unavailable and error paths, instead of deriving the count
from isRead.
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet+Attachments.swift (1)

47-105: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Duplicated attachment-staging diagnostics loop.

stageSelectedPhotos (lines 47-105) and stageSelectedFiles (lines 107-154) each implement the same attachmentPreparationStarted/Succeeded/Failed recording pattern, including identical CancellationError handling, inside otherwise-similar staging loops.

Extract the shared staging-with-diagnostics logic (start event, success event with correlationID/count, cancellation handling, failure classification) into one helper that both callers use with a source-specific staging closure. This reduces the risk that a future diagnostic change is applied to one path but not the other.

Also applies to: 107-154

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

In
`@Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet`+Attachments.swift
around lines 47 - 105, The attachment preparation diagnostics are duplicated
between stageSelectedPhotos and stageSelectedFiles. Extract their shared start,
success, cancellation, and failure-classification behavior into a helper that
accepts a source-specific staging closure, then update both methods to use it
while preserving each path’s existing staging and cleanup behavior.
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet+DirectorySelection.swift (1)

14-21: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Record .taskDirectorySearchSucceeded only for search selections

choose(path:) uses the same callback for search results and the browse screen’s “Use” action. Pass the selection source to selectDirectory(_:) and emit this event only for search selections.

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

In
`@Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet`+DirectorySelection.swift
around lines 14 - 21, Update selectDirectory(_:) to accept a selection-source
indicator, and have choose(path:) pass whether the path came from search or
browsing. Keep the directory update behavior unchanged, but record
.taskDirectorySearchSucceeded only when the source is a search selection, not
from the browse screen’s “Use” action.
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView+AgentChat.swift (1)

170-195: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

One refresh can record both .chatSessionListLoadFailed and .chatSessionListLoadSucceeded.

When source.sessions(...) throws and store.chatSessionListFailureMeansUnsupported(error) returns true, the catch records .chatSessionListLoadFailed and then sets seedOutcome = .authoritative([]). That outcome has canInvalidateSelection == true, so line 189 also records .chatSessionListLoadSucceeded with the same diagnosticStartedAt.

The log then holds two mutually exclusive terminal events for one operation. Any success-rate or latency derivation over this taxonomy becomes wrong.

Record the failure only when the error is a real failure. Treat the "unsupported" case as its own outcome.

🔧 Proposed fix that keeps one terminal event per refresh
         do {
             seedOutcome = .authoritative(try await source.sessions(workspaceID: workspaceID))
         } catch {
-            store.recordAppEvent(
-                .chatSessionListLoadFailed,
-                correlationID: workspaceID,
-                startedAt: diagnosticStartedAt,
-                failure: DiagnosticFailureKind.classify(error)
-            )
-            seedOutcome = store.chatSessionListFailureMeansUnsupported(error)
-                ? .authoritative([])
-                : .unavailable
+            if store.chatSessionListFailureMeansUnsupported(error) {
+                // The host does not offer chat sessions. This is an
+                // authoritative empty list, not a load failure.
+                seedOutcome = .authoritative([])
+            } else {
+                store.recordAppEvent(
+                    .chatSessionListLoadFailed,
+                    correlationID: workspaceID,
+                    startedAt: diagnosticStartedAt,
+                    failure: DiagnosticFailureKind.classify(error)
+                )
+                seedOutcome = .unavailable
+            }
         }
🤖 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`+AgentChat.swift
around lines 170 - 195, Update the session refresh flow around source.sessions
and seedOutcome so an unsupported error is represented as its own non-success
outcome rather than .authoritative([]), while preserving the empty-session
behavior for applying results. Ensure .chatSessionListLoadSucceeded is not
recorded for unsupported failures, leaving exactly one terminal event per
refresh; retain failure logging for genuine errors.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@ios/cmuxPackage/Sources/cmuxFeature/MobileAuthComposition.swift`:
- Around line 176-180: Update the authentication restore reporting around
hadCachedSessionAtLaunch so intentional cache clears from clearAuthRequested or
clearStaleAuthOnLaunch are not recorded as .authRestoreFailed. Use the
coordinator’s attempted-restore state to gate .authRestoreFailed, and record a
separate skipped-restore event when launch options intentionally clear the
cached session.

In
`@Packages/iOS/CmuxAgentChatUI/Sources/CmuxAgentChatUI/Composer/ChatComposerView.swift`:
- Around line 547-566: Update the attachment handling flow around the picker
staging loop and final `attachments = staged` assignment to preserve pasted
attachments while replacing only previously picker-backed attachments. Track
each attachment’s source with clear ownership, calculate picker capacity from
the remaining four-item limit after existing non-picker attachments, and
restrict selection accordingly; retain the existing preparation diagnostics and
ensure the total attachment count never exceeds the limit.

In
`@Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/BackingUpPairedMacStore.swift`:
- Around line 1133-1142: Update the guard failure in the paired-Mac lookup
within the backup write flow to classify a missing local record as local unknown
state rather than endpoint unavailable. Change the diagnosticLog?.recordAppEvent
call for .pairedMacBackupWriteFailed to use the appropriate .unknown failure
value, while preserving the existing correlation ID and control flow.

In
`@Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite.swift`:
- Around line 7005-7018: Update the error log in the setCustomization catch
block to mark both macDeviceID and the String(describing: error) interpolation
as privacy: .private, while preserving the existing message and event recording
behavior.

In
`@Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite`+NotificationDismissSync.swift:
- Line 77: Update the error interpolation in the notification dismiss sync
logging statement to use SwiftLog privacy .private instead of .public, while
keeping the count interpolation and existing log context unchanged.

In
`@Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite`+NotificationFeed.swift:
- Around line 55-59: Update notification feed telemetry to reflect refresh
outcomes: at the aggregate event near lines 55-59, derive success from the
target refresh results and emit failure when none apply; at the per-refresh
handling near lines 899-905, emit a superseded or stale outcome instead of
.notificationFeedLoadSucceeded when applied is false.

In
`@Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite`+PairedMacPersistence.swift:
- Around line 191-203: Update performSerializedPairedMacWrite and its caller so
a scope-change early return is treated as a skipped write: set accepted to false
or return whether the serialized closure executed, record a superseded failure
event for that path, and emit pairedMacStoreWriteSucceeded and
computerRoutesUpdated only when the write actually occurred.

In
`@Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite`+TaskComposer.swift:
- Around line 190-193: In both task-template persistence guards within
MobileShellComposite+TaskComposer.swift (lines 190-193 and 214-217), separate
the failure causes: record .superseded only when signed out or
capturedGeneration is stale, and record .endpointUnavailable when
taskTemplateStore is nil. Apply the same classification to both draft
persistence and submission-settings persistence.

In
`@Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite`+WorkspaceActions.swift:
- Around line 627-633: Update the authorization-failure early-return branch in
the workspace mutation function, where
disconnectForAuthorizationFailureIfNeeded(error) returns true, to record
diagnosticKinds.failure with failureKind before returning. Preserve the existing
workspaceMutationFailure return behavior and avoid duplicating the event in the
normal failure path.

In
`@Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite`+WorkspaceListRecovery.swift:
- Around line 124-135: Capture a stable snapshot of foregroundMacDeviceID before
recording workspaceListRecoveryStarted, then use that snapshot as the
correlationID for both the start event and the deferred
workspaceListRecoverySucceeded/workspaceListRecoveryFailed event. Keep the
existing recovery status, cancellation, and count handling unchanged.

In
`@Packages/iOS/CmuxMobileShell/Tests/CmuxMobileShellTests/ComposerPendingAttachmentTests.swift`:
- Around line 56-60: Replace the fixed 100-iteration Task.yield polling with a
DiagnosticLog completion signal or deadline-bounded poll that verifies three
events are processed before snapshotting in ComposerPendingAttachmentTests.swift
(lines 56-60). Apply the same change in MobileConnectionMethodStoreTests.swift
(lines 49-53), waiting for one processed event; preserve the existing assertions
and snapshot behavior.

In
`@Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/CMUXMobileRootView.swift`:
- Around line 340-349: Update the Task in CMUXMobileRootView around
connectPairingURL(rawURL) to capture and use the result returned by
connectPairingURLResult(_:) instead of reading store.connectionState. Classify
.appOpenURLHandled, .appOpenURLRejected, and the corresponding failure from that
operation result, matching the existing connectAttachURL(_:) behavior.
- Around line 294-306: Make the async operations explicitly owned, cancellable,
and safe against superseded results: in CMUXMobileRootView.swift:294-306, have
AuthCoordinator own and serialize the revalidation task or bind it to a
cancellable view lifecycle; in MobileIrohSettingsModel.swift:142-166, retain and
cancel relay-test tasks by relay ID and ignore completions from superseded
tests; in MobileIrohSettingsModel.swift:199-214, make mutation work caller-owned
or retain it in the model so cancellation and supersession are explicit,
eliminating unowned fire-and-forget Task instances.

In
`@Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/OnboardingFlowView.swift`:
- Around line 70-76: Update the stage-change diagnostic flow around
captureSceneViewed so .onboardingStageChanged is recorded only when the stage
differs from the previously tracked stage, excluding initial onAppear and
authentication-only changes; preserve scene capture behavior for all existing
triggers and update the previous-stage value after handling the transition.

In
`@Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet.swift`:
- Around line 294-296: Update TaskComposerSheet initialization to store an
authoritative typed restoration flag immediately after computing
restoredOperationID, using whether that identifier is present. In the .onAppear
event block, replace the taskTemplateStore?.composerDraft() disk re-read with
this stored flag so .draftRestored is emitted only when initialization actually
restored the draft and no extra persistence load occurs.

In
`@Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet`+Attachments.swift:
- Around line 27-45: Update the limit-rejection event calls in the attachment
picker presentation methods to include a distinct failure classification for the
attachment-count limit, while preserving the existing attachments.count payload.
Add or use a dedicated event kind/code for count-limit failures, and do not
replace it with the broader resource-limit event alone; apply the same
classification consistently in both presentAttachmentPhotoPicker and
presentAttachmentFileImporter.

In
`@Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet`+CompletedOperationRecovery.swift:
- Around line 44-67: Update the .failure(.alreadyCompleted) branch in the result
handling switch to record the mapped diagnostic failure kind for
MobileWorkspaceMutationFailure.alreadyCompleted. Use .protocolViolation or bind
the failure and reuse failure.diagnosticFailureKind instead of recording
.endpointUnavailable, while preserving the existing recovery messaging and
reconciliation behavior.

In
`@Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TerminalArtifactFilesSheet.swift`:
- Around line 201-205: Update the placeholder `sessionLoader` initialization
flow in `loadInitial` and its failure branches to create
`ChatArtifactLoader.unsupported(...)` with the available `diagnosticLog`,
matching the pattern used by `WorkspaceChatPane` and `WorkspaceDetailView`.
Ensure every placeholder assigned before or instead of the resolved loader
receives the same diagnostic log, without attempting to access the environment
value during property initialization.

In
`@Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TerminalComposerView.swift`:
- Around line 406-429: Defer the cancellation check in the isPickerPresented
dismissal branch until the end of the current run-loop turn, after
pickerSelection’s onChange has settled. Re-evaluate pickerHadSelection inside
the deferred callback before recording photoPickerCancelled, while keeping
photoPickerDidDismiss and the existing selection tracking behavior unchanged.

In
`@Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TerminalShortcutsSettingsView.swift`:
- Around line 160-166: The durable diagnostic log uses count inconsistently
across producers. In
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TerminalShortcutsSettingsView.swift:160-166,
update recordShortcutChange and recordCustomActionChange to encode the action
through distinct event codes or a dedicated enumerated payload field instead of
count. In
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TerminalComposerView.swift:686-701,
stop placing attachment count and byte total in count for the same
attachmentPreparationFailed/resourceLimitReached event pair; use one documented
unit or separate failure kinds. Document count’s unit for every affected event
code in the diagnostic taxonomy.

In
`@Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceChangesSheet.swift`:
- Around line 200-216: Update the file-diff loading flow in
WorkspaceChangesSheet so .fileDiffLoadSucceeded is recorded only after
store.fetchFileDiff completes and the response is parsed successfully, while
thrown fetch or parse errors record .fileDiffLoadFailed. Move .fileDiffExpanded
from before the fetch to the successful terminal path, preserving maxLines as
its count; record cache hits with a documented count value or omit count, using
.fileDiffCacheHit and .fileDiffLoadFailed if those event types are not yet
defined.

In
`@Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView.swift`:
- Around line 989-993: Update browser creation instrumentation across
openLocalBrowserFallback and openBrowserFromToolbar so each start, success, and
failure event uses the same correlation ID for a single creation attempt. Record
the Mac-panel creation attempt and its outcome, emit a failure event when Mac
creation is rejected, and only record browserCreateSucceeded after an actual
successful creation rather than after fallback.

In
`@Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView`+AgentChat.swift:
- Around line 587-626: Extract the shared dictation event/failure mapping from
TerminalComposerView.recordDictationDiagnostic and
TerminalComposerView.dictationFailure, including exhaustive handling of
ComposerDictationUnavailabilityReason, into one reusable mapping. Update both
call sites to use it so all nine dictation cases and future reason cases remain
consistent and compiler-checked.

In
`@Packages/iOS/CmuxMobileShellUI/Tests/CmuxMobileShellUITests/MobileDisplaySettingsTests.swift`:
- Around line 24-27: Replace the fixed Task.yield loops that wait for
DiagnosticLog processing with deterministic completion synchronization: use a
completion signal or a deadline-bounded poll of the real processedCount() >= 2
predicate. Apply the same change in
Packages/iOS/CmuxMobileShellUI/Tests/CmuxMobileShellUITests/MobileDisplaySettingsTests.swift#L24-L27
and
Packages/iOS/CmuxMobileShellUI/Tests/CmuxMobileShellUITests/MobileIrohSettingsModelTests.swift#L28-L31;
preserve the assertions after both diagnostic events are processed.

In
`@Packages/iOS/CmuxMobileShellUI/Tests/CmuxMobileShellUITests/MobileIrohSettingsModelTests.swift`:
- Around line 38-40: Update the test method containing the report encoding
assertion to be async throws, replace the optional encoding with throwing
JSONEncoder().encode(report), and decode the resulting data directly so encoding
failures fail the test instead of producing an empty string.

In
`@Packages/iOS/CmuxMobileToast/Tests/CmuxMobileToastTests/ToastCenterTests.swift`:
- Around line 19-22: Replace the fixed 100-iteration Task.yield loop in
ToastCenterTests with deterministic synchronization for DiagnosticLog: await its
completion signal after enqueuing both events, or, if unavailable, poll
processedCount() until it reaches 2 with a deadline and fail clearly on timeout.
Preserve the assertion that both events are processed before continuing.

In
`@Packages/Shared/CmuxAgentChat/Sources/CmuxAgentChat/Store/ChatConversationStore.swift`:
- Around line 608-619: Update appendToWindow(_:) to compute freshMessages by
excluding knownWindowIDs before calculating artifactCount. Count only .fileEdit
and .attachment entries from freshMessages, then emit artifactDiscovered so
replayed messages cannot generate duplicate diagnostics.

In
`@Packages/Shared/CMUXMobileCore/Sources/CMUXMobileCore/DiagnosticEventPresentation.swift`:
- Around line 644-682: Replace the hardcoded unknown fallback in
terminalToolbarActionName, terminalZoomActionName, primaryTabName,
searchScopeName, toolbarConfigurationActionName, feedbackRouteName,
toastStyleName, and toastDismissReasonName with one shared localized value using
the stable key diagnostics.unknown.appEventValue and an English defaultValue,
matching the existing appEventName/failureName localization pattern. Add the
corresponding en and ja catalog entries to Localizable.xcstrings, preserving the
raw value in the rendered unknown(...) text.

In
`@Packages/Shared/CmuxSentryTelemetry/Tests/CmuxSentryReportingTests/TransportSentryReporterTests.swift`:
- Around line 282-285: Add an immediate `#expect`(logs.count == 2) after
retrieving logs and before any indexed access in the relevant test, then retain
the existing attribute assertions for logs[0] and logs[1].

---

Outside diff comments:
In
`@Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite`+NotificationFeed.swift:
- Around line 160-208: Update the notification read-state mutation flow around
the target guard and all notificationFeedItemMarkedRead recordAppEvent calls to
skip event reporting when item.isRead already equals isRead, while preserving
the no-op behavior. For actual read or unread mutations, always report count: 1,
including endpoint-unavailable and error paths, instead of deriving the count
from isRead.

In
`@Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet`+Attachments.swift:
- Around line 47-105: The attachment preparation diagnostics are duplicated
between stageSelectedPhotos and stageSelectedFiles. Extract their shared start,
success, cancellation, and failure-classification behavior into a helper that
accepts a source-specific staging closure, then update both methods to use it
while preserving each path’s existing staging and cleanup behavior.

In
`@Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet`+DirectorySelection.swift:
- Around line 14-21: Update selectDirectory(_:) to accept a selection-source
indicator, and have choose(path:) pass whether the path came from search or
browsing. Keep the directory update behavior unchanged, but record
.taskDirectorySearchSucceeded only when the source is a search selection, not
from the browse screen’s “Use” action.

In
`@Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView`+AgentChat.swift:
- Around line 170-195: Update the session refresh flow around source.sessions
and seedOutcome so an unsupported error is represented as its own non-success
outcome rather than .authoritative([]), while preserving the empty-session
behavior for applying results. Ensure .chatSessionListLoadSucceeded is not
recorded for unsupported failures, leaving exactly one terminal event per
refresh; retain failure logging for genuine errors.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 8899fcbf-e3e0-4ff3-8953-1c0aa7bbf316

📥 Commits

Reviewing files that changed from the base of the PR and between d64a84a and eddf44f.

📒 Files selected for processing (121)
  • Packages/Shared/CMUXMobileCore/Sources/CMUXMobileCore/DiagnosticAppEventDetail.swift
  • Packages/Shared/CMUXMobileCore/Sources/CMUXMobileCore/DiagnosticCorrelation.swift
  • Packages/Shared/CMUXMobileCore/Sources/CMUXMobileCore/DiagnosticEventCode.swift
  • Packages/Shared/CMUXMobileCore/Sources/CMUXMobileCore/DiagnosticEventPresentation.swift
  • Packages/Shared/CMUXMobileCore/Sources/CMUXMobileCore/DiagnosticLog.swift
  • Packages/Shared/CMUXMobileCore/Sources/CMUXMobileCore/DiagnosticTaxonomy.swift
  • Packages/Shared/CMUXMobileCore/Sources/CMUXMobileCore/Resources/Localizable.xcstrings
  • Packages/Shared/CMUXMobileCore/Tests/CMUXMobileCoreTests/AppLogTests.swift
  • Packages/Shared/CMUXMobileCore/Tests/CMUXMobileCoreTests/DiagnosticEventPresentationTests.swift
  • Packages/Shared/CMUXMobileCore/Tests/CMUXMobileCoreTests/DiagnosticLogTests.swift
  • Packages/Shared/CmuxAgentChat/Sources/CmuxAgentChat/Store/ChatConversationDiagnosticEvent.swift
  • Packages/Shared/CmuxAgentChat/Sources/CmuxAgentChat/Store/ChatConversationStore.swift
  • Packages/Shared/CmuxSentryTelemetry/Sources/CmuxSentryReporting/TransportSentryReporter.swift
  • Packages/Shared/CmuxSentryTelemetry/Tests/CmuxSentryReportingTests/TransportSentryReporterTests.swift
  • Packages/iOS/CmuxAgentChatUI/Sources/CmuxAgentChatUI/Artifacts/ChatArtifactFolderView.swift
  • Packages/iOS/CmuxAgentChatUI/Sources/CmuxAgentChatUI/Artifacts/ChatArtifactInlineViewer.swift
  • Packages/iOS/CmuxAgentChatUI/Sources/CmuxAgentChatUI/Artifacts/ChatArtifactLoader.swift
  • Packages/iOS/CmuxAgentChatUI/Sources/CmuxAgentChatUI/Artifacts/ChatArtifactViewerModel.swift
  • Packages/iOS/CmuxAgentChatUI/Sources/CmuxAgentChatUI/Artifacts/ChatArtifactViewerPageModel.swift
  • Packages/iOS/CmuxAgentChatUI/Sources/CmuxAgentChatUI/Artifacts/ChatArtifactViewerPager.swift
  • Packages/iOS/CmuxAgentChatUI/Sources/CmuxAgentChatUI/Artifacts/ChatArtifactViewerPagerModel.swift
  • Packages/iOS/CmuxAgentChatUI/Sources/CmuxAgentChatUI/Composer/ChatComposerView.swift
  • Packages/iOS/CmuxAgentChatUI/Sources/CmuxAgentChatUI/Screen/ChatScreen.swift
  • Packages/iOS/CmuxAgentChatUI/Sources/CmuxAgentChatUI/Transcript/ChatRowActions.swift
  • Packages/iOS/CmuxAgentChatUI/Sources/CmuxAgentChatUI/Transcript/Rows/ChatPermissionCardView.swift
  • Packages/iOS/CmuxAgentChatUI/Sources/CmuxAgentChatUI/Transcript/Rows/ChatQuestionCardView.swift
  • Packages/iOS/CmuxMobileAnalytics/Sources/CmuxMobileAnalytics/AnalyticsEmitter.swift
  • Packages/iOS/CmuxMobileBrowser/Sources/CmuxMobileBrowser/BrowserSurfaceDiagnosticEvent.swift
  • Packages/iOS/CmuxMobileBrowser/Sources/CmuxMobileBrowser/MobileBrowserPane.swift
  • Packages/iOS/CmuxMobileBrowser/Sources/CmuxMobileBrowser/MobileBrowserView.swift
  • Packages/iOS/CmuxMobileBrowserStream/Sources/CmuxMobileBrowserStream/BrowserStreamEventReceiving.swift
  • Packages/iOS/CmuxMobileBrowserStream/Sources/CmuxMobileBrowserStream/BrowserStreamStore.swift
  • Packages/iOS/CmuxMobileCamera/Sources/CmuxMobileCamera/QRCodeCaptureController.swift
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/BackingUpPairedMacStore.swift
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileChatEventSource+TerminalArtifacts.swift
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileChatEventSource.swift
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobilePairingFailure.swift
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+AgentChat.swift
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+AppDiagnostics.swift
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+BrowserStream.swift
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+ConnectionRecovery.swift
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+Helpers.swift
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+HiddenMacs.swift
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+NotificationDismissSync.swift
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+NotificationFeed.swift
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+PairedMacPersistence.swift
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+SimulatorStream.swift
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+StateSync.swift
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+TaskAttachments.swift
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+TaskComposer.swift
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+TaskDirectoryList.swift
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+TaskModels.swift
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+TerminalOutputDelivery.swift
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+TerminalScrollDelivery.swift
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+TerminalTheme.swift
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+TerminalViewport.swift
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+WorkspaceActions.swift
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+WorkspaceChanges.swift
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+WorkspaceChangesContent.swift
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+WorkspaceCreateRequest.swift
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+WorkspaceListRecovery.swift
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite.swift
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileTaskDirectoryListFailure.swift
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileTaskTemplateStore.swift
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileWorkspaceMutationFailure.swift
  • Packages/iOS/CmuxMobileShell/Tests/CmuxMobileShellTests/ComposerPendingAttachmentTests.swift
  • Packages/iOS/CmuxMobileShellModel/Sources/CmuxMobileShellModel/MobileConnectionMethodStore.swift
  • Packages/iOS/CmuxMobileShellModel/Tests/CmuxMobileShellModelTests/MobileConnectionMethodStoreTests.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/CMUXMobileRootView.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/GhosttySurfaceCoordinator+Artifacts.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/MobileDiagnosticsEnvironment.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/MobileDisplaySettings.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/MobileIrohSettingsModel.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/MobileIrohSettingsView.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/MobilePairingScannerSheet.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/MobilePushCoordinator.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/MobileSettingsAccountSection.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/MobileSettingsView.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/MobileShellComposite+WorkspaceChangesArtifactLoader.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/NotificationFeedPreviewView.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/NotificationFeedStoreView.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/NotificationFeedView.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/OnboardingFlowView.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/QRCodeScannerView.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/Resources/Localizable.xcstrings
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/SignInView.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet+Attachments.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet+CompletedOperationRecovery.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet+DirectorySelection.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet+DraftState.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet+ModelSelection.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TerminalArtifactFilesSheet.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TerminalComposerView.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TerminalShortcutsSettingsView.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceChangesSheet.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceChatPane.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView+AgentChat.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView+Surfaces.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView+TerminalArtifacts.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceListView+DragDrop.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceListView.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceShellView.swift
  • Packages/iOS/CmuxMobileShellUI/Tests/CmuxMobileShellUITests/MobileDisplaySettingsTests.swift
  • Packages/iOS/CmuxMobileShellUI/Tests/CmuxMobileShellUITests/MobileIrohSettingsModelTests.swift
  • Packages/iOS/CmuxMobileSupport/Sources/CmuxMobileSupport/ComposerDictationController.swift
  • Packages/iOS/CmuxMobileSupport/Sources/CmuxMobileSupport/ComposerDictationDiagnosticEvent.swift
  • Packages/iOS/CmuxMobileTerminal/Sources/CmuxMobileTerminal/GhosttySurfaceView.swift
  • Packages/iOS/CmuxMobileTerminal/Sources/CmuxMobileTerminal/GhosttySurfaceViewDelegate.swift
  • Packages/iOS/CmuxMobileTerminal/Sources/CmuxMobileTerminal/TerminalInputAccessoryAction.swift
  • Packages/iOS/CmuxMobileTerminal/Sources/CmuxMobileTerminal/TerminalInputTextView.swift
  • Packages/iOS/CmuxMobileToast/Sources/CmuxMobileToast/ToastCenter.swift
  • Packages/iOS/CmuxMobileToast/Tests/CmuxMobileToastTests/ToastCenterTests.swift
  • ios/cmux/AppCompositionRoot.swift
  • ios/cmux/CmuxAppDelegate.swift
  • ios/cmux/Resources/Localizable.xcstrings
  • ios/cmux/cmuxApp.swift
  • ios/cmuxPackage/Sources/cmuxFeature/CMUXMobileRootScene.swift
  • ios/cmuxPackage/Sources/cmuxFeature/MobileAnalyticsComposition.swift
  • ios/cmuxPackage/Sources/cmuxFeature/MobileAuthComposition.swift

Comment thread ios/cmuxPackage/Sources/cmuxFeature/MobileAuthComposition.swift Outdated
Comment thread Packages/iOS/CmuxMobileToast/Tests/CmuxMobileToastTests/ToastCenterTests.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: 8

Caution

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

⚠️ Outside diff range comments (3)
Packages/Shared/CMUXMobileCore/Tests/CMUXMobileCoreTests/DiagnosticLogTests.swift (1)

841-848: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Await the tap callback through a completion signal.

The fixed 1,000-yield loop can finish before the drain task invokes the tap on a busy executor. The assertion can then fail although the event is retained. Capture live through an AsyncStream or continuation and await that callback before asserting.

As per coding guidelines: “Tests must await real completion signals or deadline-bounded polls of real predicates rather than fixed-duration waits before assertions.”

🤖 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/Shared/CMUXMobileCore/Tests/CMUXMobileCoreTests/DiagnosticLogTests.swift`
around lines 841 - 848, Replace the fixed 1,000-iteration Task.yield loop in the
tap-delivery test with an explicit completion signal, such as an AsyncStream or
continuation, fulfilled by the live tap callback. Await that signal before
asserting received events, while preserving the existing event-retention and
callback behavior.

Source: Coding guidelines

Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+TaskComposer.swift (1)

270-276: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Do not report an agent launch for a plain-shell submission.

finish(.success) always records .taskAgentLaunched. TaskComposerSheet uses this submission path for plain-shell templates, where the user action is “Open Shell” rather than an agent launch. Record this event only when spec starts an agent. Use a separate shell event if required.

🤖 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`+TaskComposer.swift
around lines 270 - 276, The success-event flow in finish(.success) currently
records .taskAgentLaunched for every submission. Gate that event on whether spec
starts an agent, preserving .taskSubmitSucceeded and .taskWorkspaceCreated for
plain-shell submissions; use the appropriate separate shell event if one already
exists.
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet.swift (1)

875-905: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Record template mutation success only after a store mutation occurs.

taskTemplateStore?.addTemplate, updateTemplate, and deleteTemplates are no-ops when the store is unavailable. The following event still reports creation, update, or deletion. Guard the store before mutation, then emit the success event only after that mutation. Record an unavailable-state failure when diagnostics need to capture the missing store.

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

In
`@Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet.swift`
around lines 875 - 905, Update addTemplate, updateTemplate, and deleteTemplates
to require an available taskTemplateStore before performing mutations or
recording success events. Return early when the store is unavailable, and record
the appropriate unavailable-state failure event if the existing diagnostics API
supports it; emit taskTemplateCreated, taskTemplateUpdated, or
taskTemplateDeleted only after the corresponding store mutation succeeds.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@ios/cmux/MobileAppLifecycleDiagnostics.swift`:
- Around line 9-11: Add a concise safety comment immediately above
MobileAppLifecycleDiagnostics declaring `@unchecked` Sendable, explaining that
notificationCenter and observers are immutable and the NSObjectProtocol
collection is the sole reason compiler verification is unavailable.
- Line 22: Update the packaged iOS privacy manifest PrivacyInfo.xcprivacy to
declare the screenshot diagnostic persisted in the App Log and forwarded through
Sentry, including the applicable diagnostic and crash data categories. Align the
manifest comment with the Sentry integration while preserving the existing
consent wording and product-interaction declaration.

In
`@Packages/iOS/CmuxAgentChatUI/Sources/CmuxAgentChatUI/Composer/ChatComposerView.swift`:
- Around line 92-94: Update the onDisappear handler in ChatComposerView to set
isStagingAttachments = false after cancelling attachmentStaging, ensuring
lifecycle cancellation clears the staging state and does not block performSend()
when the composer reappears.

In
`@Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite`+NotificationFeed.swift:
- Around line 759-772: Update the aggregateOutcome handling in the notification
feed retry loop so .failed remains sticky when no refresh has applied: a later
.stale result must not replace an earlier failure, while .applied should still
become the final outcome. Preserve the existing retry and fetchNotificationFeed
behavior.

In
`@Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/MobileIrohSettingsModel.swift`:
- Around line 71-82: Update refresh() to use a refreshToken UUID like mutate and
testCustomRelay: assign a new token when starting the task, capture it in the
task, and only clear refreshTask and refreshToken if the captured token still
matches. Add the `@ObservationIgnored` refreshToken property alongside the
existing operation tokens, and ensure cancellation leaves no stale task handle
when the current refresh exits early.
- Around line 339-343: Remove task cancellation from the deinit methods in
MobileIrohSettingsModel and MobileAuthComposition. Add MainActor-isolated
teardown methods in both owners to cancel refreshTask, mutationTask,
relayTestTasks, and the corresponding task properties, then ensure each teardown
method is called before the instance is released.

In
`@Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/MobileIrohSettingsView.swift`:
- Line 178: Update the onDisappear cleanup in MobileIrohSettingsView so it no
longer calls model.cancelOperations(), which cancels user-initiated mutationTask
writes. Split the cleanup to cancel only the refresh and relay-test tasks, while
allowing mutateAndWait controller writes to continue after the view disappears.

In
`@Packages/iOS/CmuxMobileShellUI/Tests/CmuxMobileShellUITests/MobileIrohSettingsModelTests.swift`:
- Around line 168-181: Update the affected test method in
MobileIrohSettingsModelTests to be async throws, then use try `#require` to verify
log.processedCount() is at least 4 after the deadline loop. After taking the
snapshot, require report.events.count == 4 before accessing report.events[1],
preserving the existing event and failure assertions.

---

Outside diff comments:
In
`@Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite`+TaskComposer.swift:
- Around line 270-276: The success-event flow in finish(.success) currently
records .taskAgentLaunched for every submission. Gate that event on whether spec
starts an agent, preserving .taskSubmitSucceeded and .taskWorkspaceCreated for
plain-shell submissions; use the appropriate separate shell event if one already
exists.

In
`@Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet.swift`:
- Around line 875-905: Update addTemplate, updateTemplate, and deleteTemplates
to require an available taskTemplateStore before performing mutations or
recording success events. Return early when the store is unavailable, and record
the appropriate unavailable-state failure event if the existing diagnostics API
supports it; emit taskTemplateCreated, taskTemplateUpdated, or
taskTemplateDeleted only after the corresponding store mutation succeeds.

In
`@Packages/Shared/CMUXMobileCore/Tests/CMUXMobileCoreTests/DiagnosticLogTests.swift`:
- Around line 841-848: Replace the fixed 1,000-iteration Task.yield loop in the
tap-delivery test with an explicit completion signal, such as an AsyncStream or
continuation, fulfilled by the live tap callback. Await that signal before
asserting received events, while preserving the existing event-retention and
callback behavior.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 30230448-23e6-4f8a-8d86-0e59eb193af5

📥 Commits

Reviewing files that changed from the base of the PR and between eddf44f and ed114d6.

📒 Files selected for processing (61)
  • Packages/Shared/CMUXMobileCore/Sources/CMUXMobileCore/DiagnosticAppEventDetail.swift
  • Packages/Shared/CMUXMobileCore/Sources/CMUXMobileCore/DiagnosticCorrelation.swift
  • Packages/Shared/CMUXMobileCore/Sources/CMUXMobileCore/DiagnosticEventPresentation.swift
  • Packages/Shared/CMUXMobileCore/Sources/CMUXMobileCore/DiagnosticLog.swift
  • Packages/Shared/CMUXMobileCore/Sources/CMUXMobileCore/DiagnosticTaxonomy.swift
  • Packages/Shared/CMUXMobileCore/Sources/CMUXMobileCore/Resources/Localizable.xcstrings
  • Packages/Shared/CMUXMobileCore/Tests/CMUXMobileCoreTests/DiagnosticEventPresentationTests.swift
  • Packages/Shared/CMUXMobileCore/Tests/CMUXMobileCoreTests/DiagnosticLogTests.swift
  • Packages/Shared/CmuxAgentChat/Sources/CmuxAgentChat/Store/ChatConversationDiagnosticEvent.swift
  • Packages/Shared/CmuxAgentChat/Sources/CmuxAgentChat/Store/ChatConversationStore.swift
  • Packages/Shared/CmuxSentryTelemetry/Tests/CmuxSentryReportingTests/TransportSentryReporterTests.swift
  • Packages/iOS/CmuxAgentChatUI/Sources/CmuxAgentChatUI/Artifacts/ChatArtifactFolderView.swift
  • Packages/iOS/CmuxAgentChatUI/Sources/CmuxAgentChatUI/Composer/ChatComposerAttachment.swift
  • Packages/iOS/CmuxAgentChatUI/Sources/CmuxAgentChatUI/Composer/ChatComposerPasteboard.swift
  • Packages/iOS/CmuxAgentChatUI/Sources/CmuxAgentChatUI/Composer/ChatComposerView.swift
  • Packages/iOS/CmuxAgentChatUI/Sources/CmuxAgentChatUI/Screen/ChatScreen.swift
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/BackingUpPairedMacStore.swift
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileChatEventSource.swift
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+AppDiagnostics.swift
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+BrowserStream.swift
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+NotificationDismissSync.swift
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+NotificationFeed.swift
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+PairedMacPersistence.swift
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+TaskComposer.swift
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+WorkspaceActions.swift
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite+WorkspaceListRecovery.swift
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/MobileShellComposite.swift
  • Packages/iOS/CmuxMobileShell/Sources/CmuxMobileShell/NotificationFeedFetchOutcome.swift
  • Packages/iOS/CmuxMobileShell/Tests/CmuxMobileShellTests/ComposerPendingAttachmentTests.swift
  • Packages/iOS/CmuxMobileShellModel/Tests/CmuxMobileShellModelTests/MobileConnectionMethodStoreTests.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/CMUXMobileRootView.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/GhosttySurfaceCoordinator+Artifacts.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/MobileComposerDictationDiagnostics.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/MobileIrohSettingsModel.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/MobileIrohSettingsView.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/MobilePushCoordinator.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/NotificationFeedStoreView.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/OnboardingFlowView.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerAttachmentPickerModifier.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet+Attachments.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet+CompletedOperationRecovery.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TaskComposer/TaskComposerSheet.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TerminalArtifactFilesSheet.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TerminalComposerView.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/TerminalShortcutsSettingsView.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceChangesSheet.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceChatPane.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView+AgentChat.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView+TerminalArtifacts.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceShellView.swift
  • Packages/iOS/CmuxMobileShellUI/Tests/CmuxMobileShellUITests/MobileDisplaySettingsTests.swift
  • Packages/iOS/CmuxMobileShellUI/Tests/CmuxMobileShellUITests/MobileIrohSettingsModelTests.swift
  • Packages/iOS/CmuxMobileToast/Sources/CmuxMobileToast/ToastCenter.swift
  • Packages/iOS/CmuxMobileToast/Tests/CmuxMobileToastTests/ToastCenterTests.swift
  • ios/cmux-ios.xcodeproj/project.pbxproj
  • ios/cmux/AppCompositionRoot.swift
  • ios/cmux/CmuxAppDelegate.swift
  • ios/cmux/MobileAppLifecycleDiagnostics.swift
  • ios/cmux/cmuxApp.swift
  • ios/cmuxPackage/Sources/cmuxFeature/MobileAuthComposition.swift
💤 Files with no reviewable changes (3)
  • Packages/iOS/CmuxAgentChatUI/Sources/CmuxAgentChatUI/Artifacts/ChatArtifactFolderView.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView+TerminalArtifacts.swift
  • ios/cmux/cmuxApp.swift

Comment on lines +9 to +11
final class MobileAppLifecycleDiagnostics: @unchecked Sendable {
private let notificationCenter: NotificationCenter
private let observers: [NSObjectProtocol]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Document the @unchecked Sendable rationale.

Both stored properties are immutable, and [NSObjectProtocol] is the only reason the compiler cannot prove Sendable. State that in the code so the escape hatch is auditable.

♻️ Proposed change
+/// `@unchecked Sendable`: every stored property is immutable after `init`, and
+/// the observer tokens are only read in `deinit`. The opt-out exists solely
+/// because `[NSObjectProtocol]` carries no `Sendable` conformance.
 final class MobileAppLifecycleDiagnostics: `@unchecked` Sendable {
     private let notificationCenter: NotificationCenter
     private let observers: [NSObjectProtocol]

As per coding guidelines, "Do not mark shared mutable reference types as Sendable unless they use ... @unchecked Sendable with a clear safety explanation."

📝 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
final class MobileAppLifecycleDiagnostics: @unchecked Sendable {
private let notificationCenter: NotificationCenter
private let observers: [NSObjectProtocol]
/// `@unchecked Sendable`: every stored property is immutable after `init`, and
/// the observer tokens are only read in `deinit`. The opt-out exists solely
/// because `[NSObjectProtocol]` carries no `Sendable` conformance.
final class MobileAppLifecycleDiagnostics: `@unchecked` Sendable {
private let notificationCenter: NotificationCenter
private let observers: [NSObjectProtocol]
🤖 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 `@ios/cmux/MobileAppLifecycleDiagnostics.swift` around lines 9 - 11, Add a
concise safety comment immediately above MobileAppLifecycleDiagnostics declaring
`@unchecked` Sendable, explaining that notificationCenter and observers are
immutable and the NSObjectProtocol collection is the sole reason compiler
verification is unavailable.

Source: Coding guidelines

(UIApplication.didReceiveMemoryWarningNotification, .appMemoryWarningReceived),
(UIApplication.protectedDataWillBecomeUnavailableNotification, .appProtectedDataUnavailable),
(UIApplication.protectedDataDidBecomeAvailableNotification, .appProtectedDataAvailable),
(UIApplication.userDidTakeScreenshotNotification, .appScreenshotCaptured),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check the privacy manifest and consent copy for behavioral telemetry coverage.
fd -t f 'PrivacyInfo.xcprivacy' --exec cat {}
rg -nP --type=swift -C3 'appScreenshotCaptured'
rg -nPi 'telemetry|analytics' --iglob '*.xcstrings' -l

Repository: manaflow-ai/cmux

Length of output: 5911


🏁 Script executed:

#!/bin/bash
set -u

echo '--- target and related files ---'
fd -t f -i 'MobileAppLifecycleDiagnostics.swift|PrivacyInfo.xcprivacy|.*xcstrings|.*Consent.*|.*Telemetry.*|.*Analytics.*' .

echo '--- diagnostics source ---'
if [ -f ios/cmux/MobileAppLifecycleDiagnostics.swift ]; then
  wc -l ios/cmux/MobileAppLifecycleDiagnostics.swift
  cat -n ios/cmux/MobileAppLifecycleDiagnostics.swift
fi

echo '--- all event references ---'
rg -n -S -C4 'appScreenshotCaptured|userDidTakeScreenshotNotification|ScreenshotCaptured|screenshot' --glob '!**/DerivedData/**' --glob '!**/.git/**' .

echo '--- telemetry and consent references ---'
rg -n -i -C3 'telemetry|analytics|consent|opt.?out|sentry|breadcrumb|diagnostic' ios .github --glob '*.{swift,xcstrings,plist,json,md,yml,yaml}' 2>/dev/null | head -1200

echo '--- tracked privacy manifests ---'
git ls-files '*PrivacyInfo.xcprivacy' | while IFS= read -r f; do
  echo "### $f"
  cat -n "$f"
done

Repository: manaflow-ai/cmux

Length of output: 50373


🏁 Script executed:

#!/bin/bash
set -u

echo '--- tracked target ---'
git ls-files -- 'ios/cmux/MobileAppLifecycleDiagnostics.swift' '*PrivacyInfo.xcprivacy' '*.xcstrings'

echo '--- target source ---'
cat -n ios/cmux/MobileAppLifecycleDiagnostics.swift

echo '--- exact event references ---'
rg -n -F -C6 'appScreenshotCaptured' --glob '!**/.git/**' .
rg -n -F -C6 'userDidTakeScreenshotNotification' --glob '!**/.git/**' .

echo '--- telemetry/consent identifiers in iOS app ---'
rg -n -i -C4 'telemetry|analytics|consent|opt.?out|sentry|breadcrumb|durable|diagnostic' ios/cmux --glob '*.swift' --glob '*.xcstrings' --glob '*.plist' | head -800

echo '--- privacy manifests with paths and relevant keys ---'
git ls-files '*PrivacyInfo.xcprivacy' | while IFS= read -r f; do
  echo "### $f"
  rg -n -C3 'NSPrivacyCollectedDataType|NSPrivacyTracking|ProductInteraction|Analytics|Diagnostic|Crash|UserID|DeviceID|EmailAddress' "$f"
done

Repository: manaflow-ai/cmux

Length of output: 50372


🏁 Script executed:

#!/bin/bash
set -u

echo '--- exact implementation files ---'
rg -l -S 'class DiagnosticLog|struct DiagnosticLog|recordAppEvent|setEventTap|class TransportSentryReporter|struct TransportSentryReporter|isTelemetryEnabled|UserDefaultsAnalyticsConsentProvider|telemetryAnalyticsOnlyFooter|telemetryFooter' ios Packages/Shared Packages/iOS --glob '*.{swift,xcstrings,plist}' | sort -u

echo '--- DiagnosticLog and event-tap implementation ---'
rg -n -S -C12 'recordAppEvent|setEventTap|eventTap|func ingest|class DiagnosticLog|actor DiagnosticLog|struct DiagnosticLog' ios Packages/Shared Packages/iOS --glob '*.swift' | head -1000

echo '--- Sentry reporter and consent implementation ---'
rg -n -S -C14 'TransportSentryReporter|Sentry.*Reporter|isTelemetryEnabled|UserDefaultsAnalyticsConsentProvider|RevocationWatcher|SentrySDK.isEnabled|startIfEnabled' ios Packages/Shared Packages/iOS --glob '*.swift' | head -1400

echo '--- telemetry UI and exact localized copy ---'
rg -n -S -C8 'telemetryAnalyticsOnlyFooter|telemetryFooter|mobile.settings.telemetry|Share Analytics|product analytics|crash reports|App Log records' ios Packages/iOS Packages/Shared --glob '*.{swift,xcstrings}' | head -1000

echo '--- both iOS privacy manifests ---'
for f in ios/cmux/PrivacyInfo.xcprivacy ios/cmux/Resources/PrivacyInfo.xcprivacy; do
  echo "### $f"
  cat -n "$f"
done

Repository: manaflow-ai/cmux

Length of output: 50373


🏁 Script executed:

#!/bin/bash
set -u

echo '--- reporter implementation ---'
f=$(rg -l -S 'public final class TransportSentryReporter' Packages/Shared --glob '*.swift' | head -1)
echo "### $f"
cat -n "$f" | sed -n '1,260p'

echo '--- DiagnosticLog implementation locations ---'
rg -l -S 'recordAppEvent|setEventTap' Packages/Shared ios --glob '*.swift' | grep -v '/Tests/' | sort -u

echo '--- privacy manifest summaries ---'
python3 - <<'PY'
import plistlib
from pathlib import Path
for p in [Path("ios/cmux/PrivacyInfo.xcprivacy"), Path("ios/cmux/Resources/PrivacyInfo.xcprivacy")]:
    print(f"### {p}")
    with p.open("rb") as fh:
        d = plistlib.load(fh)
    print("tracking:", d.get("NSPrivacyTracking"))
    for item in d.get("NSPrivacyCollectedDataTypes", []):
        print({
            "type": item.get("NSPrivacyCollectedDataType"),
            "linked": item.get("NSPrivacyCollectedDataTypeLinked"),
            "tracking": item.get("NSPrivacyCollectedDataTypeTracking"),
            "purposes": item.get("NSPrivacyCollectedDataTypePurposes"),
        })
PY

echo '--- telemetry strings and usage ---'
rg -n -S -C12 'mobile.settings.telemetry|Share Analytics|product analytics|crash reports|App Log records|diagnostic' ios/cmux/Resources/Localizable.xcstrings ios/cmux --glob '*.swift' | grep -E 'mobile.settings.telemetry|Share Analytics|product analytics|crash reports|App Log records|diagnostic|telemetry' | head -500

Repository: manaflow-ai/cmux

Length of output: 24095


🏁 Script executed:

#!/bin/bash
set -u

echo '--- manifest references and target membership ---'
rg -n -C6 'PrivacyInfo\.xcprivacy|PrivacyInfo' ios --glob '*.{pbxproj,pbxproj,xcconfig,swift,plist,json,yml,yaml}' | head -500

echo '--- telemetry settings usage ---'
rg -n -S -C12 'telemetryAnalyticsOnly|telemetryFooter|mobile.settings.telemetry|Share Analytics and Crash Reports|AnalyticsConsentProviding|isTelemetryEnabled' ios Packages/iOS Packages/Shared --glob '*.swift' | head -1000

echo '--- DiagnosticLog persistence and event classification ---'
f=Packages/Shared/CMUXMobileCore/Sources/CMUXMobileCore/DiagnosticLog.swift
cat -n "$f" | sed -n '1,280p'
f2=Packages/Shared/CMUXMobileCore/Sources/CMUXMobileCore/DiagnosticTaxonomy.swift
rg -n -C10 'appScreenshotCaptured|isAppFeatureDiagnosticEvent|DiagnosticAppEventKind' "$f2"

Repository: manaflow-ai/cmux

Length of output: 50373


Declare the screenshot diagnostic in the packaged iOS privacy manifest.

ios/cmux/PrivacyInfo.xcprivacy declares only NSPrivacyCollectedDataTypeProductInteraction, but appScreenshotCaptured is also persisted in the App Log and forwarded to Sentry as a breadcrumb and possible structured log when consent is enabled. Add the applicable diagnostic and crash data declarations. Keep the manifest comment aligned with the Sentry integration. The existing consent copy covers product analytics and crash reports.

🤖 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 `@ios/cmux/MobileAppLifecycleDiagnostics.swift` at line 22, Update the packaged
iOS privacy manifest PrivacyInfo.xcprivacy to declare the screenshot diagnostic
persisted in the App Log and forwarded through Sentry, including the applicable
diagnostic and crash data categories. Align the manifest comment with the Sentry
integration while preserving the existing consent wording and
product-interaction declaration.

Comment on lines +759 to +772
var aggregateOutcome = NotificationFeedFetchOutcome.failed
repeat {
self.notificationFeedRefreshPendingMacIDs.remove(macDeviceID)
let requiredRevision =
self.notificationFeedKnownRevisionsByMac[macDeviceID] ?? -1
_ = await self.fetchNotificationFeed(
let outcome = await self.fetchNotificationFeed(
macDeviceID: macDeviceID,
client: client,
displayName: displayName,
requiredRevision: requiredRevision
)
if outcome == .applied || aggregateOutcome != .applied {
aggregateOutcome = outcome
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Preserve a failed refresh outcome.

If an immediate retry first returns .failed and later returns .stale, Line 771 replaces the failure. Lines 63-69 then report .superseded instead of an endpoint failure when no refresh applied. Keep .failed sticky until an .applied outcome occurs.

Proposed fix
-            var aggregateOutcome = NotificationFeedFetchOutcome.failed
+            var aggregateOutcome: NotificationFeedFetchOutcome?
             repeat {
                 ...
-                if outcome == .applied || aggregateOutcome != .applied {
+                if aggregateOutcome != .applied,
+                   (outcome == .failed || aggregateOutcome != .failed) {
                     aggregateOutcome = outcome
                 }
                 ...
-            return aggregateOutcome
+            return aggregateOutcome ?? .failed
📝 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
var aggregateOutcome = NotificationFeedFetchOutcome.failed
repeat {
self.notificationFeedRefreshPendingMacIDs.remove(macDeviceID)
let requiredRevision =
self.notificationFeedKnownRevisionsByMac[macDeviceID] ?? -1
_ = await self.fetchNotificationFeed(
let outcome = await self.fetchNotificationFeed(
macDeviceID: macDeviceID,
client: client,
displayName: displayName,
requiredRevision: requiredRevision
)
if outcome == .applied || aggregateOutcome != .applied {
aggregateOutcome = outcome
}
var aggregateOutcome: NotificationFeedFetchOutcome?
repeat {
self.notificationFeedRefreshPendingMacIDs.remove(macDeviceID)
let requiredRevision =
self.notificationFeedKnownRevisionsByMac[macDeviceID] ?? -1
let outcome = await self.fetchNotificationFeed(
macDeviceID: macDeviceID,
client: client,
displayName: displayName,
requiredRevision: requiredRevision
)
if aggregateOutcome != .applied,
(outcome == .failed || aggregateOutcome != .failed) {
aggregateOutcome = outcome
}
🤖 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`+NotificationFeed.swift
around lines 759 - 772, Update the aggregateOutcome handling in the notification
feed retry loop so .failed remains sticky when no refresh has applied: a later
.stale result must not replace an earlier failure, while .applied should still
become the final outcome. Preserve the existing retry and fetchNotificationFeed
behavior.

Comment on lines 71 to 82
func refresh() {
Task {
await controller.refreshIrohSettings()
snapshot = await controller.irohSettingsSnapshot()
await reloadDiagnostics()
refreshTask?.cancel()
refreshTask = Task { @MainActor [weak self] in
guard let self else { return }
await self.controller.refreshIrohSettings()
guard !Task.isCancelled else { return }
self.snapshot = await self.controller.irohSettingsSnapshot()
await self.reloadDiagnostics()
guard !Task.isCancelled else { return }
self.refreshTask = nil
}
}

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 | 🟡 Minor | ⚡ Quick win

Track refreshTask with a token like the other operations.

refresh() replaces refreshTask without a supersession token. Two failure modes follow.

  1. If a prior refresh finishes its last await before the cancel takes effect, it reaches Line 80 and sets refreshTask = nil, which discards the handle of the newer task. cancelOperations() and deinit can then no longer cancel that task.
  2. If the task is cancelled, Line 79 returns early and leaves refreshTask pointing at a finished, cancelled task until the next refresh().

Use the same token pattern as mutate and testCustomRelay.

🔧 Proposed fix
     func refresh() {
         refreshTask?.cancel()
+        let token = UUID()
+        refreshToken = token
         refreshTask = Task { `@MainActor` [weak self] in
             guard let self else { return }
             await self.controller.refreshIrohSettings()
-            guard !Task.isCancelled else { return }
+            guard !Task.isCancelled, self.refreshToken == token else { return }
             self.snapshot = await self.controller.irohSettingsSnapshot()
             await self.reloadDiagnostics()
-            guard !Task.isCancelled else { return }
-            self.refreshTask = nil
+            guard self.refreshToken == token else { return }
+            self.refreshTask = nil
+            self.refreshToken = nil
         }
     }

Add the token storage next to the existing ones:

`@ObservationIgnored` private var refreshToken: UUID?
🤖 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/MobileIrohSettingsModel.swift`
around lines 71 - 82, Update refresh() to use a refreshToken UUID like mutate
and testCustomRelay: assign a new token when starting the task, capture it in
the task, and only clear refreshTask and refreshToken if the captured token
still matches. Add the `@ObservationIgnored` refreshToken property alongside the
existing operation tokens, and ensure cancellation leaves no stale task handle
when the current refresh exits early.

Comment on lines +339 to +343
deinit {
refreshTask?.cancel()
mutationTask?.cancel()
relayTestTasks.values.forEach { $0.cancel() }
}

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 | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Determine Swift language mode / default isolation for the affected packages and targets.
fd -t f 'Package.swift' Packages/iOS/CmuxMobileShellUI ios/cmuxPackage --exec cat -n {}
rg -nP 'SWIFT_(VERSION|DEFAULT_ACTOR_ISOLATION|STRICT_CONCURRENCY|UPCOMING_FEATURE)' ios/cmux-ios.xcodeproj/project.pbxproj ios/Config 2>/dev/null
# Find existing precedents of deinit inside MainActor-isolated types.
ast-grep run --pattern $'`@MainActor`
$KIND $NAME {
  $$$
  deinit {
    $$$
  }
  $$$
}' --lang swift Packages/iOS ios

Repository: manaflow-ai/cmux

Length of output: 11108


🌐 Web query:

Swift 6 nonisolated deinit access to MainActor isolated stored properties rules

💡 Result:

In Swift 6, attempting to access MainActor-isolated stored properties from a standard, non-isolated deinitializer is disallowed and will trigger a compile-time error [1][2]. This restriction exists because a standard deinitializer is not guaranteed to run on the MainActor executor; it is executed whenever the object's final reference is released, which can occur on any thread [1][2]. To safely access MainActor-isolated properties or perform MainActor-isolated cleanup during deinitialization, Swift provides the isolated deinit feature [3][4]. Key Rules and Mechanisms: 1. Isolated Deinit: By declaring a deinitializer as isolated deinit, you instruct the Swift runtime to schedule the object's deallocation and the execution of the deinit body on the actor's executor (in this case, the MainActor) [3][5]. This allows safe, synchronous access to the class's isolated stored properties [3][4]. 2. Default Isolation: Implicitly synthesized deinitializers are nonisolated by default [3][5][6]. If you need MainActor isolation, you must explicitly declare isolated deinit [3][5]. 3. Inheritance Rules: If a base class has an isolated deinit, all derived classes must also have an isolated deinit with the same isolation to maintain safety [3][5]. 4. Compatibility: This feature is intended for classes and actors that require cleanup of non-sendable, isolated state [3][5]. For types that do not require custom cleanup logic, the default nonisolated deinitializer remains appropriate and safe for releasing references [3][5]. In scenarios where isolated deinit is not available or appropriate, developers are traditionally advised to perform cleanup in an explicit lifecycle method (e.g., stop() or invalidate()) called before the object is deallocated, or to ensure that properties are compatible with non-isolated access, such as using nonisolated(unsafe) if thread safety can be externally guaranteed [7].

Citations:


🏁 Script executed:

#!/bin/bash
set -u

for f in \
  Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/MobileIrohSettingsModel.swift \
  ios/cmuxPackage/Sources/cmuxFeature/MobileAuthComposition.swift
do
  echo "=== $f ==="
  rg -n -C 12 '(`@MainActor`|class MobileIrohSettingsModel|class MobileAuthTaskOwner|struct MobileAuthTaskOwner|deinit|refreshTask|mutationTask|relayTestTasks|restoreTask|revalidationTask)' "$f"
done

echo "=== package language settings ==="
rg -n -C 3 'swiftLanguageMode|SWIFT_VERSION|SWIFT_DEFAULT_ACTOR_ISOLATION|SWIFT_STRICT_CONCURRENCY|SWIFT_UPCOMING_FEATURE' \
  Packages/iOS/CmuxMobileShellUI/Package.swift \
  ios/cmuxPackage/Package.swift \
  ios/cmux-ios.xcodeproj/project.pbxproj 2>/dev/null || true

echo "=== related task-owner declarations ==="
rg -n -C 8 'class (MobileIrohSettingsModel|MobileAuthTaskOwner)|`@MainActor`.*(MobileIrohSettingsModel|MobileAuthTaskOwner)' \
  Packages/iOS ios

Repository: manaflow-ai/cmux

Length of output: 21534


🌐 Web query:

Swift Evolution SE-0371 isolated synchronous deinit Swift 6 availability Task cancel MainActor

💡 Result:

Swift Evolution proposal SE-0371, "Isolated synchronous deinit," was implemented and made available in Swift 6.2 [1][2]. It allows deinitializers for actors and global-actor isolated classes to be explicitly marked as isolated deinit [1][2]. Key characteristics and behaviors of isolated deinit include: * Purpose and Functionality: It enables deinit to access non-sendable isolated state by ensuring the deinit body executes on the actor's executor or the relevant global actor's executor [1]. This lifts restrictions previously imposed on deinit when dealing with actor-isolated state [1]. * Syntax: You declare it using the isolated keyword, for example: isolated deinit { ... } [1][3]. * Scheduling: If the object is already on the correct executor, the deinit runs synchronously [1]. If switching is required, the runtime schedules a task-less job on the target executor with the same priority as the thread that released the last reference to the object [1]. * Task Locals: To ensure consistency and avoid reliance on implementation details, task locals are cleared when entering an isolated deinit [4][5]. They do not retain the values from the scope where the object was released [4][6]. * Implicit Deinitializers: Implicitly synthesized deinitializers are nonisolated by default [3]. If you write an explicit deinit, you must mark it with isolated or the appropriate global actor attribute if you wish to maintain isolation [3]. Regarding your query on Swift 6 compatibility: This feature is not part of the initial Swift 6.0 release; it was finalized for Swift 6.2 [1][7]. Because it requires runtime support for these specific execution semantics, it may have minimum OS version requirements (e.g., iOS 18.4+), and usage in earlier versions or with older OS deployment targets may not be supported or may be restricted to experimental flags in newer compilers [7].

Citations:


Move task cancellation out of both deinit methods. The affected SwiftPM targets use Swift 6, and ordinary deinit cannot access these @MainActor-isolated task properties. Add MainActor teardown methods and call them before release in both owners.

📍 Affects 2 files
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/MobileIrohSettingsModel.swift#L339-L343 (this comment)
  • ios/cmuxPackage/Sources/cmuxFeature/MobileAuthComposition.swift#L398-L401
🤖 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/MobileIrohSettingsModel.swift`
around lines 339 - 343, Remove task cancellation from the deinit methods in
MobileIrohSettingsModel and MobileAuthComposition. Add MainActor-isolated
teardown methods in both owners to cancel refreshTask, mutationTask,
relayTestTasks, and the corresponding task properties, then ensure each teardown
method is called before the instance is released.

.navigationTitle(L10n.string("mobile.iroh.title", defaultValue: "Iroh and Relays"))
.navigationBarTitleDisplayMode(.inline)
.task { await model.observe() }
.onDisappear { model.cancelOperations() }

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check whether the Iroh settings controller mutations are cancellation-aware mid-write.
ast-grep outline Packages/iOS --items all --match 'CmxIrohSettingsControlling'
rg -nP --type=swift -C5 'func (setPathPreference|setPreference|upsertCustomRelay|upsertCustomPrivatePath|removeCustomRelay)\s*\(' -g '!**/Tests/**'

Repository: manaflow-ai/cmux

Length of output: 168


🏁 Script executed:

#!/bin/bash
set -eu
target='Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/MobileIrohSettingsView.swift'
printf '%s\n' '--- target outline ---'
ast-grep outline "$target" --items all
printf '%s\n' '--- target size ---'
wc -l "$target"
printf '%s\n' '--- relevant symbols ---'
rg -n -C8 --type=swift 'cancelOperations|mutationTask|mutateAndWait|onDisappear|Relay Only|custom relay|setPathPreference|upsertCustom|removeCustom' Packages/iOS
printf '%s\n' '--- matching model/controller files ---'
rg -l --type=swift 'cancelOperations|mutationTask|mutateAndWait|IrohSettings' Packages/iOS

Repository: manaflow-ai/cmux

Length of output: 50373


🏁 Script executed:

#!/bin/bash
set -eu
target='Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/MobileIrohSettingsView.swift'
printf '%s\n' '--- target lines 1-230 ---'
sed -n '1,230p' "$target"
printf '%s\n' '--- target lines 430-664 ---'
sed -n '430,664p' "$target"
printf '%s\n' '--- exact symbol locations ---'
rg -n --type=swift 'MobileIrohSettings(Model|Controller)|cancelOperations|mutationTask|mutateAndWait|setPathPreference|upsertCustomRelay|upsertCustomPrivatePath|removeCustomPrivatePath|startRelayTest|refresh' Packages/iOS/CmuxMobileShellUI

Repository: manaflow-ai/cmux

Length of output: 50372


🏁 Script executed:

#!/bin/bash
set -eu
model='Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/MobileIrohSettingsModel.swift'
printf '%s\n' '--- model outline ---'
ast-grep outline "$model" --items all
printf '%s\n' '--- model implementation ---'
sed -n '1,380p' "$model"
printf '%s\n' '--- controller protocol and implementations ---'
rg -n -C6 --type=swift 'CmxIrohSettingsControlling|refreshIrohSettings|setIrohPathPreference|upsertIrohCustomRelay|removeIrohCustomRelay|upsertIrohCustomPrivatePath|removeIrohCustomPrivatePath' Packages

Repository: manaflow-ai/cmux

Length of output: 50372


🏁 Script executed:

#!/bin/bash
set -eu
python3 - <<'PY'
from pathlib import Path
import re

model = Path("Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/MobileIrohSettingsModel.swift").read_text()
view = Path("Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/MobileIrohSettingsView.swift").read_text()

checks = {
    "view cancels all operations": r"\.onDisappear\s*\{\s*model\.cancelOperations\(\)\s*\}",
    "cancelOperations cancels mutationTask": r"func cancelOperations\(\).*?mutationTask\?\.cancel\(\)",
    "mutateAndWait checks before operation": r"func mutateAndWait\(.*?try Task\.checkCancellation\(\)\s*try await operation\(\)",
    "mutateAndWait checks after operation": r"try await operation\(\)\s*try Task\.checkCancellation\(\)",
    "path preference uses mutate": r"func setPathPreference\(.*?mutate\(",
    "custom relay save uses mutateAndWait": r"func upsertCustomRelay\(.*?await mutateAndWait\(",
    "private path save uses mutateAndWait": r"func upsertCustomPrivatePath\(.*?await mutateAndWait\(",
}

for label, pattern in checks.items():
    source = view if label == "view cancels all operations" else model
    found = re.search(pattern, source, re.S) is not None
    print(f"{label}: {'yes' if found else 'no'}")

print("controller declarations:")
for path in Path("Packages").rglob("*.swift"):
    text = path.read_text(errors="replace")
    if "protocol CmxIrohSettingsControlling" in text:
        print(path)
        start = text.index("protocol CmxIrohSettingsControlling")
        print(text[start:start + 1800])
PY
printf '%s\n' '--- production protocol references ---'
rg -n --glob '*.swift' --glob '!**/Tests/**' 'CmxIrohSettingsControlling|func (setIrohPathPreference|upsertIrohCustomRelay|upsertIrohCustomPrivatePath)' Packages

Repository: manaflow-ai/cmux

Length of output: 5596


Keep user-initiated settings writes alive after the view disappears.

cancelOperations() cancels mutationTask. mutateAndWait can then stop before the controller write or record .cancelled after it completes. Split cleanup so disappearance cancels only refresh and relay-test tasks.

🤖 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/MobileIrohSettingsView.swift`
at line 178, Update the onDisappear cleanup in MobileIrohSettingsView so it no
longer calls model.cancelOperations(), which cancels user-initiated mutationTask
writes. Split the cleanup to cancel only the refresh and relay-test tasks, while
allowing mutateAndWait controller writes to continue after the view disappears.

Comment on lines +168 to +181
let clock = ContinuousClock()
let deadline = clock.now.advanced(by: .seconds(1))
while await log.processedCount() < 4, clock.now < deadline {
await Task.yield()
}

let report = await log.snapshot()
#expect(report.events.map(\.a) == [
DiagnosticAppEventKind.irohCustomRelayTestStarted.rawValue,
DiagnosticAppEventKind.irohCustomRelayTestFailed.rawValue,
DiagnosticAppEventKind.irohCustomRelayTestStarted.rawValue,
DiagnosticAppEventKind.irohCustomRelayTestSucceeded.rawValue,
])
#expect(report.events[1].b == DiagnosticFailureKind.superseded.rawValue)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu

file="Packages/iOS/CmuxMobileShellUI/Tests/CmuxMobileShellUITests/MobileIrohSettingsModelTests.swift"
printf '%s\n' '--- target test ---'
sed -n '130,195p' "$file"

printf '%s\n' '--- nearby event-log declarations and uses ---'
rg -n -C 4 'processedCount|snapshot|struct .*Log|actor .*Log|events' "$file"

Repository: manaflow-ai/cmux

Length of output: 15226


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- DiagnosticLog definitions ---'
rg -n -C 12 'actor DiagnosticLog|final class DiagnosticLog|struct DiagnosticLog|processedCount\(\)|func snapshot\(\)' \
  --glob '*.swift' .

printf '%s\n' '--- test wait helper and package test configuration ---'
sed -n '260,320p' Packages/iOS/CmuxMobileShellUI/Tests/CmuxMobileShellUITests/MobileIrohSettingsModelTests.swift
rg -n -C 5 'SwiftTesting|Testing|swift-testing|`#require`|`#expect`' \
  Packages/iOS/CmuxMobileShellUI/Package.swift Packages/iOS/CmuxMobileShellUI/Tests 2>/dev/null || true

Repository: manaflow-ai/cmux

Length of output: 50373


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- files defining or using processedCount ---'
rg -l --glob '*.swift' 'processedCount' . | head -50

printf '%s\n' '--- exact DiagnosticLog references ---'
rg -n --glob '*.swift' '\bDiagnosticLog\b' . | head -100

printf '%s\n' '--- target imports and wait helper ---'
sed -n '1,12p' Packages/iOS/CmuxMobileShellUI/Tests/CmuxMobileShellUITests/MobileIrohSettingsModelTests.swift
sed -n '262,275p' Packages/iOS/CmuxMobileShellUI/Tests/CmuxMobileShellUITests/MobileIrohSettingsModelTests.swift

Repository: manaflow-ai/cmux

Length of output: 15453


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- DiagnosticLog implementation ---'
sed -n '1,260p' Packages/Shared/CMUXMobileCore/Sources/CMUXMobileCore/DiagnosticLog.swift

printf '%s\n' '--- DiagnosticLog processing tests ---'
sed -n '1,75p' Packages/Shared/CMUXMobileCore/Tests/CMUXMobileCoreTests/DiagnosticLogTests.swift
sed -n '300,350p' Packages/Shared/CMUXMobileCore/Tests/CMUXMobileCoreTests/DiagnosticLogTests.swift

Repository: manaflow-ai/cmux

Length of output: 16728


🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- relay test implementation ---'
rg -n -C 16 'testCustomRelay|irohCustomRelayTestStarted|irohCustomRelayTestFailed|irohCustomRelayTestSucceeded' \
  Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/MobileIrohSettingsModel.swift

printf '%s\n' '--- DiagnosticLog barrier and snapshot declarations ---'
rg -n -C 10 'func processedCount|func snapshot|func append\(' \
  Packages/Shared/CMUXMobileCore/Sources/CMUXMobileCore/DiagnosticLog.swift

Repository: manaflow-ai/cmux

Length of output: 8522


🏁 Script executed:

#!/bin/bash
set -eu

python3 - <<'PY'
from pathlib import Path
import re

test = Path("Packages/iOS/CmuxMobileShellUI/Tests/CmuxMobileShellUITests/MobileIrohSettingsModelTests.swift").read_text()
log = Path("Packages/Shared/CMUXMobileCore/Sources/CMUXMobileCore/DiagnosticLog.swift").read_text()
model = Path("Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/MobileIrohSettingsModel.swift").read_text()

# Read-only structural checks for the relevant behavior.
assert "while await log.processedCount() < 4, clock.now < deadline" in test
assert "report.events[1]" in test
assert "snapshot(generatedAt: Date = Date())" in log
assert "Events still pending in the non-blocking stream are not forced to drain" in log
assert "totalProcessed += 1" in log

# The timeout path is valid: the loop may exit with any processed count below 4.
states = []
for processed in range(4):
    events = list(range(processed))  # no more than processed events can be retained
    try:
        events[1]
        indexed_access = "valid"
    except IndexError:
        indexed_access = "trap"
    states.append((processed, len(events), indexed_access))

print("timeout states:", states)
assert any(state[2] == "trap" for state in states)
assert all((count >= 2) == (access == "valid")
           for _, count, access in states)

# The intended path records two starts plus superseded failure and success.
assert model.count(".irohCustomRelayTestStarted") >= 1
assert model.count(".irohCustomRelayTestFailed") >= 1
assert ".irohCustomRelayTestSucceeded" in model
print("result: the deadline can leave an insufficient event array, and report.events[1] can trap")
PY

Repository: manaflow-ai/cmux

Length of output: 243


🏁 Script executed:

#!/bin/bash
set -eu

python3 - <<'PY'
from pathlib import Path

test = Path("Packages/iOS/CmuxMobileShellUI/Tests/CmuxMobileShellUITests/MobileIrohSettingsModelTests.swift").read_text()
log = Path("Packages/Shared/CMUXMobileCore/Sources/CMUXMobileCore/DiagnosticLog.swift").read_text()
model = Path("Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/MobileIrohSettingsModel.swift").read_text()

checks = {
    "deadline loop": "while await log.processedCount() < 4, clock.now < deadline" in test,
    "indexed access": "report.events[1]" in test,
    "non-forcing snapshot": "Events still pending in the non-blocking stream are not forced to" in log,
    "processed count declaration": "public func processedCount() async -> Int" in log,
    "append increments count": "totalProcessed" in log and "processedCount()" in log,
}
for name, result in checks.items():
    print(f"{name}: {result}")
assert all(checks.values())

states = []
for processed in range(4):
    events = list(range(processed))
    try:
        events[1]
        access = "valid"
    except IndexError:
        access = "trap"
    states.append((processed, len(events), access))

print("timeout states:", states)
assert any(access == "trap" for _, _, access in states)
assert states[0][2] == "trap"
assert states[1][2] == "trap"
assert states[2][2] == "valid"
print("result: a timeout can leave report.events[1] out of range")
PY

Repository: manaflow-ai/cmux

Length of output: 427


Guard the event count before indexed access.

Change the test to async throws. Use try #require(await log.processedCount() >= 4) after the deadline loop, then require report.events.count == 4 before accessing report.events[1].

🤖 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/MobileIrohSettingsModelTests.swift`
around lines 168 - 181, Update the affected test method in
MobileIrohSettingsModelTests to be async throws, then use try `#require` to verify
log.processedCount() is at least 4 after the deadline loop. After taking the
snapshot, require report.events.count == 4 before accessing report.events[1],
preserving the existing event and failure assertions.

Source: Learnings

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes using default effort and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 5d0e66d. Configure here.

@cursor

cursor Bot commented Aug 11, 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.

…e-logging

# Conflicts:
#	Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/CMUXMobileRootView.swift
#	Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/OnboardingFlowView.swift
#	Sources/PortalSplitDividerCacheInvalidator.swift
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