Skip to content

Prepare iOS App Store review readiness - #7862

Merged
azooz2003-bit merged 60 commits into
mainfrom
task-ios-appstore-review-readiness
Jul 13, 2026
Merged

azooz2003-bit merged 60 commits into
mainfrom
task-ios-appstore-review-readiness

Conversation

@azooz2003-bit

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

Copy link
Copy Markdown
Collaborator

Summary

  • update the live privacy policy source for iOS data, permissions, auth, push, and account deletion
  • add official Apple ID 6783338052 reviewer setup notes for a prepared review Mac and manual pairing
  • block direct App Store billing portal requests before Stack or Stripe work
  • remove native callback scheme passthrough from App Store unavailable billing redirects

Testing

  • RESEND_API_KEY=test CMUX_FEEDBACK_FROM_EMAIL=feedback@example.com CMUX_FEEDBACK_RATE_LIMIT_ID=test STACK_SECRET_SERVER_KEY=test NEXT_PUBLIC_STACK_PROJECT_ID=test NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY=test bun test web/tests/billing-portal-route.test.ts web/tests/billing-checkout-route.test.ts web/tests/app-pricing-page.test.tsx
  • cd web && bun run typecheck
  • local dev server on CMUX_PORT=4827: / returned 200; /api/billing/portal?cmux_distribution=appstore&cmux_scheme=cmux returned 302 to /app-pricing?cmux_app=1&cmux_distribution=appstore&billing=unavailable

Notes

  • /handler/sign-in and /handler/after-sign-in local warmup require real Stack dev credentials; none are present in ~/.secrets/cmuxterm-dev.env on this machine.

Note

High Risk
Touches consent-gated telemetry, Sentry lifecycle, and App Store release automation; regressions could leak analytics after opt-out or block/shape submission. App Store crash reporting is deliberately off until consent is proven.

Overview
Prepares the official App Store lane (Apple ID 6783338052) with CI that creates/updates ASC versions, applies local metadata, validates and uploads iPhone 6.9" and iPad screenshots before archive/upload, and tightens release validation to those device types.

Privacy & compliance: iOS telemetry now defaults off until the user enables Share Analytics (and crash reports when enabled) in Settings via sendAnonymousTelemetry. Adds PrivacyInfo.xcprivacy, localized privacy policy content for mobile data/permissions/retention, and App Store review notes plus reviewer-setup.md for a prepared review Mac and Tailscale manual pairing.

Analytics (CmuxMobileAnalytics): Introduces a consent generation gate, UserDefaults revocation observer, and per-event consent snapshots so opt-out drops buffered/in-flight work and quick re-enable cannot resurrect pre-revocation events. HTTPAnalyticsUploader registers upload tasks and cancels them when uploads are disabled.

Crash reporting (CmuxMobileCrashReporting): Replaces revoke-only watching with bidirectional consent lifecycle (start Sentry on opt-in, stop on opt-out), dedicated URLSession invalidation before close(), and cache purging. App Store IPAs set CMUXCrashReportingEnabled=NO (beta/TestFlight stay YES); composition root and Settings copy respect that flag.

Build scripts: Upload/archive paths verify and stamp CMUX_CRASH_REPORTING_ENABLED per lane so App Store artifacts cannot ship with crash reporting enabled by mistake.

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

Summary by CodeRabbit

  • New Features
    • Added iOS Privacy settings with a toggle to control sharing analytics and crash reports.
    • Updated the privacy policy with clearer mobile data/permissions details and retention wording.
  • Bug Fixes
    • Analytics and crash reporting now default to disabled until consent is explicitly enabled.
    • App Store distribution billing requests now redirect to pricing information before starting billing flows.
    • Account deletion now removes linked analytics data and better handles cleanup failures.

@vercel

vercel Bot commented Jul 10, 2026 •

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
cmux Canceled Canceled Jul 13, 2026 5:01am
cmux-staging Building Building Preview, Comment Jul 13, 2026 5:01am

@coderabbitai

coderabbitai Bot commented Jul 10, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR adds App Store review setup and release automation, blocks App Store billing flows, introduces opt-out mobile analytics with consent-generation gating, reworks crash reporting revocation, expands privacy disclosures, and deletes PostHog account records during account deletion.

Changes

App Store review readiness

Layer / File(s) Summary
App Store billing redirect flow
web/app/api/billing/..., web/tests/billing-*
App Store checkout and portal requests redirect before billing state or Stack-user resolution.
App Review setup and instructions
ios/AppStoreReview/*
Review materials document the prepared Mac pairing flow, billing restrictions, and privacy checks.
App Store metadata and screenshot automation
.github/workflows/ios-app-store.yml
The workflow updates App Store metadata, uploads screenshots, and validates device-specific screenshot types.

Mobile analytics consent

Layer / File(s) Summary
Consent state and persistence
Packages/iOS/CmuxMobileAnalytics/..., Packages/iOS/CmuxMobileShellUI/..., ios/cmux/Resources/*
Telemetry defaults to disabled, consent is persisted through Settings, and privacy resources are packaged.
Consent-aware event buffering
Packages/iOS/CmuxMobileAnalytics/Sources/...
Events and identity updates carry consent generations and are filtered after consent changes.
Upload task cancellation
Packages/iOS/CmuxMobileAnalytics/Sources/...
Upload requests are gated and cancelled when telemetry uploads are disabled.
Consent and upload concurrency tests
Packages/iOS/CmuxMobileAnalytics/Tests/...
Tests cover consent ordering, stale generations, queued-event removal, and upload cancellation.

Crash reporting lifecycle

Layer / File(s) Summary
Consent-driven crash reporting lifecycle
Packages/iOS/CmuxMobileCrashReporting/Sources/...
Crash reporting uses injected transport and cache components and responds to consent transitions.
Crash lifecycle tests
Packages/iOS/CmuxMobileCrashReporting/Tests/...
Tests verify startup, revocation, cache purging, transport cancellation, and action ordering.

Privacy disclosures and account deletion

Layer / File(s) Summary
Mobile privacy disclosures
web/app/[locale]/(legal)/privacy-policy/page.tsx
The privacy policy documents mobile analytics, permissions, providers, pairing data, retention, and deletion behavior.
PostHog account deletion cleanup
web/app/api/account/route.ts, web/tests/account-route.test.ts
Account deletion performs PostHog person deletion before Stack deletion and tests ordering, retries, and failure handling.

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

Sequence Diagram(s)

sequenceDiagram
  participant Settings
  participant ConsentObserver
  participant AnalyticsEmitter
  participant AnalyticsUploader
  Settings->>ConsentObserver: Publish telemetry consent change
  ConsentObserver->>AnalyticsEmitter: Synchronize consent generation
  AnalyticsEmitter->>AnalyticsUploader: Enable or cancel uploads
Loading
sequenceDiagram
  participant ConsentProvider
  participant MobileCrashRevocationWatcher
  participant MobileCrashReporter
  participant TransportSession
  ConsentProvider->>MobileCrashRevocationWatcher: Report consent transition
  MobileCrashRevocationWatcher->>MobileCrashReporter: Invoke lifecycle action
  MobileCrashReporter->>TransportSession: Start or invalidate session
Loading
sequenceDiagram
  participant AccountRoute
  participant PostHog
  participant Stack
  AccountRoute->>PostHog: Delete account person
  PostHog-->>AccountRoute: Return deletion response
  AccountRoute->>Stack: Delete Stack user
Loading

Possibly related PRs

Suggested reviewers: lawrencecchen


Important

Pre-merge checks failed

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

❌ Failed checks (1 error, 1 warning)

Check name Status Explanation Resolution
Cmux No Test Or Debug Seam In Production Source ❌ Error Production Sources/PostHogAnalytics.swift adds makeForTesting and Sources/Search/SearchIndex.swift adds clearForTesting under #if DEBUG—test seams in shipping source. Drop the shipping seams; widen the needed state to internal and inspect via @testable import, or isolate any true debug tool in a dedicated debug file.
Docstring Coverage ⚠️ Warning Docstring coverage is 10.45% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (23 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Cmux Swift Actor Isolation ✅ Passed PASS: the diff keeps concurrency in actors or locked/@unchecked-Sendable helpers, and doesn’t add new implicit-MainActor protocols or unsafe shared mutable refs.
Cmux Swift Blocking Runtime ✅ Passed PASS: The only new production lock is a tiny OSAllocatedUnfairLock with a documented non-async race rationale; other new coordination is async or test-only, with no new blocking waits/sleeps.
Cmux Browser Automation Off-Main ✅ Passed The actual diff is unrelated to browser automation (only web/app/api/account/route.ts), so the off-main browser-waits rule isn’t implicated.
Cmux Expensive Synchronous Load ✅ Passed PASS: The Swift diff only touches analytics/crash setup; no new RestorableAgentSessionIndex.load() or agent-history parsing appears on @MainActor or interactive paths.
Cmux Cache Substitution Correctness ✅ Passed The PR’s cache-like consent gates are event-driven and freshness-checked; no persistence/history/undo/snapshot path replaces an authoritative read with a stale cache.
Cmux No Hacky Sleeps ✅ Passed No changed non-test TS/JS/runtime files introduce sleeps, polling, or timer-based coordination; the only timeout use is AbortSignal.timeout for PostHog deletion, which the rule allows.
Cmux Algorithmic Complexity ✅ Passed New scans are bounded (analytics batches max 100; tiny identity lists), and no unbounded nested rescans or hot-path quadratic loops were introduced.
Cmux Swift Concurrency ✅ Passed PASS: the only new custom queue is inside a NotificationCenter callback boundary, and the new Tasks are stored/cancelled or just bootstrap actor startup; no new Combine or completion-handler patterns.
Cmux Swift @Concurrent ✅ Passed No changed Swift file adds nonisolated async/@Concurrent misuse; async work is actor-isolated or intentionally UI-bound via Task.
Cmux Swift File And Package Boundaries ✅ Passed PASS: Touched production Swift files are in SwiftPM packages, 44–383 lines, and the PR adds focused analytics/crash-reporting code without app-target boundary violations.
Cmux Swiftpm Lockfiles ✅ Passed The checked commit only changes web/app/api/account/route.ts, so no SwiftPM/Xcode/dependency diff requires a Package.resolved update.
Cmux Swift Logging ✅ Passed No added print/debugPrint/dump/NSLog in touched Swift code; existing OSLog calls are sanitized and not in a MainActor-coupled changed file.
Cmux User-Facing Error Privacy ✅ Passed No changed user-facing error bodies or recovery copy expose vendor/internal details; returned errors are generic, and vendor names appear only in docs/comments/settings help.
Cmux Full Internationalization ✅ Passed All new user-facing copy is localized: privacy policy has 20 locale JSONs matching routing, and new iOS strings use L10n with en/ja catalog entries.
Cmux Swiftui State Layout ✅ Passed PASS: The changed SwiftUI view uses @Observable/@bindable models, no GeometryReader or legacy ObservableObject/@published additions, and state writes occur in handlers/onAppear.
Cmux Architecture Rethink ✅ Passed PASS: the new observers/locks are owned by the emitter/reporter/composition root, with generation gates and test gates preventing stale consent or racey state.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PASS: The PR only touches privacy, analytics, crash-reporting, and billing code/docs; no new NSWindow/WindowGroup code or cmuxAuxiliaryWindowIdentifiers changes were found.
Cmux Source Artifacts ✅ Passed The only changed path is a source file (web/app/api/account/route.ts); no logs, screenshots, caches, build output, or scratch artifacts appear in the diff.
Cmux No Ambient Global State ✅ Passed The new Swift code keeps state on instance-owned types; no new file-scope funcs/vars or runtime singletons were added. NotificationCenter.default is only a default arg.
Title check ✅ Passed The title clearly reflects the PR’s main goal: preparing iOS App Store review readiness.
Description check ✅ Passed The description includes a solid summary, testing, and notes, but it omits the demo video, review trigger block, and checklist sections.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch task-ios-appstore-review-readiness

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@greptile-apps

greptile-apps Bot commented Jul 10, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR prepares the iOS App Store review flow and updates related privacy and billing behavior.

  • App Store metadata, screenshots, reviewer notes, and release validation updates.
  • iOS analytics and crash reporting consent controls with safer revoke handling.
  • Localized privacy policy content for supported web locales.
  • App Store billing redirects that send users to pricing instead of checkout or portal flows.

Confidence Score: 5/5

This looks safe to merge.

  • No blocking issues found in the changed code.

Important Files Changed

Filename Overview
web/app/[locale]/(legal)/privacy-policy/content.ts Adds the locale-keyed privacy policy content map used by the localized route.
web/app/[locale]/(legal)/privacy-policy/page.tsx Renders privacy policy content and metadata from the selected locale.
web/app/lib/billing.ts Adds the shared App Store billing-unavailable redirect helper.
web/app/api/billing/checkout/route.ts Routes App Store checkout requests through the shared pricing-unavailable redirect.
web/app/api/billing/portal/route.ts Adds an early App Store billing portal redirect before auth and billing work.
web/tests/privacy-policy-localization.test.ts Adds tests for localized privacy policy coverage and malformed translation markers.

Reviews (40): Last reviewed commit: "Accept already-deleted analytics identit..." | Re-trigger Greptile

Comment thread web/app/[locale]/(legal)/privacy-policy/page.tsx Outdated
Comment thread web/app/lib/billing.ts Outdated
@azooz2003-bit
azooz2003-bit force-pushed the task-ios-appstore-review-readiness branch from 74c4477 to 0a8fa0f Compare July 10, 2026 22:37

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

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

Inline comments:
In `@web/app/`[locale]/(legal)/privacy-policy/page.tsx:
- Around line 24-30: Clarify the Sparkle update statement so it applies only to
the cmux macOS application, not the iPhone and iPad application. Update the
paragraph containing the Sparkle reference to explicitly say that macOS
Application updates are checked via Sparkle, while leaving iOS updates excluded
from that claim.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: a4cd4490-16fb-41af-bb41-07497e9d98b4

📥 Commits

Reviewing files that changed from the base of the PR and between 2b122c4 and 74c4477.

📒 Files selected for processing (9)
  • ios/AppStoreReview/README.md
  • ios/AppStoreReview/metadata-screenshots-checklist.md
  • ios/AppStoreReview/review-notes.md
  • ios/AppStoreReview/reviewer-setup.md
  • web/app/[locale]/(legal)/privacy-policy/page.tsx
  • web/app/api/billing/checkout/route.ts
  • web/app/api/billing/portal/route.ts
  • web/app/lib/billing.ts
  • web/tests/billing-portal-route.test.ts

Comment thread web/app/[locale]/(legal)/privacy-policy/page.tsx 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.

Caution

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

⚠️ Outside diff range comments (1)
web/app/[locale]/(legal)/privacy-policy/page.tsx (1)

67-70: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Sparkle references still say "The Application" after expanding the definition to include iOS.

The past review flagged line 68 and was marked addressed, but the current code still reads "The Application checks for updates via Sparkle." Since lines 25–27 now define "Application" to include the iPhone and iPad app, this statement is inaccurate — iOS apps update through the App Store, not Sparkle. The same ambiguity applies to the Sparkle entry in the third-party services list at line 148.

🔧 Proposed fix
       <p>
-        The Application checks for updates via Sparkle, which may transmit your
+        The macOS Application checks for updates via Sparkle, which may transmit your
         operating system version and application version to our update server.
       </p>

And for the third-party services list:

         <li>
-          <strong>Sparkle</strong> &mdash; auto-update framework. Transmits
-          application and OS version to check for updates.
+          <strong>Sparkle</strong> &mdash; macOS auto-update framework. Transmits
+          application and OS version to check for updates on macOS.
         </li>

Also applies to: 148-150

🤖 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 `@web/app/`[locale]/(legal)/privacy-policy/page.tsx around lines 67 - 70,
Clarify both Sparkle references in the privacy policy by limiting them to the
macOS application rather than the broadly defined “Application.” Update the
update-check paragraph and the Sparkle third-party-services entry so they
explicitly state that Sparkle is used by the macOS app, while iOS updates are
handled separately through the App Store.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@web/app/`[locale]/(legal)/privacy-policy/page.tsx:
- Around line 67-70: Clarify both Sparkle references in the privacy policy by
limiting them to the macOS application rather than the broadly defined
“Application.” Update the update-check paragraph and the Sparkle
third-party-services entry so they explicitly state that Sparkle is used by the
macOS app, while iOS updates are handled separately through the App Store.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 0c270db8-b648-4dc2-8ab8-cb6687dd1d21

📥 Commits

Reviewing files that changed from the base of the PR and between 74c4477 and 0a8fa0f.

📒 Files selected for processing (10)
  • ios/AppStoreReview/README.md
  • ios/AppStoreReview/metadata-screenshots-checklist.md
  • ios/AppStoreReview/review-notes.md
  • ios/AppStoreReview/reviewer-setup.md
  • web/app/[locale]/(legal)/privacy-policy/page.tsx
  • web/app/api/billing/checkout/route.ts
  • web/app/api/billing/portal/route.ts
  • web/app/lib/billing.ts
  • web/tests/billing-checkout-route.test.ts
  • web/tests/billing-portal-route.test.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (1)
web/app/[locale]/(legal)/privacy-policy/page.tsx (1)

14-14: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Localize the updated privacy-policy copy.

This [locale] page adds substantial user-facing English literals without a next-intl/catalog lookup. Non-English routes will therefore show English, and the new disclosures are missing from every supported locale catalog. Move the changed strings to locale-specific messages and add matching entries for all locales.

As per coding guidelines and path instructions, user-facing web text must use a locale-specific runtime source and be represented in every supported locale.

Also applies to: 24-30, 45-47, 57-102, 115-131, 146-162, 177-181, 259-260

🤖 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 `@web/app/`[locale]/(legal)/privacy-policy/page.tsx at line 14, Localize all
newly added user-facing text in the privacy-policy page, including the “Last
updated” text and disclosures at the referenced sections, using the project’s
next-intl/runtime message lookup pattern. Add matching message keys and
translations to every supported locale catalog, then replace the hardcoded
English literals in the page with those localized keys.

Sources: Coding guidelines, Path instructions

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

Outside diff comments:
In `@web/app/`[locale]/(legal)/privacy-policy/page.tsx:
- Line 14: Localize all newly added user-facing text in the privacy-policy page,
including the “Last updated” text and disclosures at the referenced sections,
using the project’s next-intl/runtime message lookup pattern. Add matching
message keys and translations to every supported locale catalog, then replace
the hardcoded English literals in the page with those localized keys.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 0f6d92d4-efee-4094-b1f7-866b910f2ee2

📥 Commits

Reviewing files that changed from the base of the PR and between 0a8fa0f and 115e634.

📒 Files selected for processing (4)
  • ios/AppStoreReview/review-notes.md
  • ios/AppStoreReview/reviewer-setup.md
  • web/app/[locale]/(legal)/privacy-policy/page.tsx
  • web/app/api/billing/portal/route.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (2)
web/app/[locale]/(legal)/privacy-policy/page.tsx (2)

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

Synchronize the sitemap modification date.

The page now displays July 10, 2026, but web/app/sitemap.ts:86-93 still reports /privacy-policy as last modified on March 18, 2026. Update the sitemap metadata with the policy change.

🤖 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 `@web/app/`[locale]/(legal)/privacy-policy/page.tsx at line 14, Update the
`/privacy-policy` entry in the `sitemap` function to use July 10, 2026 as its
`lastModified` date, matching the date displayed on the privacy policy page.

24-30: 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift

Localize the newly changed privacy-policy copy.

This [locale] page adds substantial hard-coded English text instead of reading from next-intl or another locale-specific source. Add catalog keys and translations for every locale in web/i18n/routing.ts; the same applies to the changed retention, analytics, permission, and third-party-service text.

As per coding guidelines, user-facing web text must use a locale-specific runtime source and be represented for every supported locale. As per path instructions, web/**/*.{ts,tsx,md,mdx,json} must apply full internationalization.

🤖 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 `@web/app/`[locale]/(legal)/privacy-policy/page.tsx around lines 24 - 30, The
privacy-policy page’s newly added English copy is hard-coded and not localized.
Update the privacy-policy page component and its changed retention, analytics,
permission, and third-party-service sections to read all user-facing text
through next-intl (or the project’s established locale source), add catalog keys
for every string, and provide translations for every locale defined in the
routing configuration.

Sources: Coding guidelines, Path instructions

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

Outside diff comments:
In `@web/app/`[locale]/(legal)/privacy-policy/page.tsx:
- Line 14: Update the `/privacy-policy` entry in the `sitemap` function to use
July 10, 2026 as its `lastModified` date, matching the date displayed on the
privacy policy page.
- Around line 24-30: The privacy-policy page’s newly added English copy is
hard-coded and not localized. Update the privacy-policy page component and its
changed retention, analytics, permission, and third-party-service sections to
read all user-facing text through next-intl (or the project’s established locale
source), add catalog keys for every string, and provide translations for every
locale defined in the routing configuration.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 47755d4d-3f1c-4cd2-86d0-529048db2ebd

📥 Commits

Reviewing files that changed from the base of the PR and between 115e634 and 1700d36.

📒 Files selected for processing (3)
  • ios/AppStoreReview/review-notes.md
  • ios/AppStoreReview/reviewer-setup.md
  • web/app/[locale]/(legal)/privacy-policy/page.tsx

Comment thread web/app/[locale]/(legal)/privacy-policy/page.tsx 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: 7

🤖 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/AppStoreReview/metadata-screenshots-checklist.md`:
- Line 58: Update the checklist entry to say that in-app Delete Account deletes
account-linked PostHog analytics before deleting the Stack user, matching the
awaited deletePostHogPersonForAccountDeletion(...) behavior.

In `@ios/AppStoreReview/reviewer-setup.md`:
- Around line 37-40: Replace the generic `100.64.0.0/10` CIDR guidance in the
`App Review Mac` setup entry with the prepared Mac’s specific Tailscale
`100.64.x.x` address or its concrete MagicDNS hostname, so reviewers have an
actual host to connect to.

In `@ios/cmux/Resources/PrivacyInfo.xcprivacy`:
- Around line 1-29: Add an NSPrivacyCollectedDataTypes array to the privacy
manifest, declaring the analytics and crash telemetry data collected by the app
with their applicable collection, tracking, and purpose details. Ensure the
entries use Apple’s required privacy manifest keys and accurately reflect the
telemetry payloads, or provide equivalent declarations through the telemetry
packages.

In
`@Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/MobileSettingsView.swift`:
- Around line 8-13: Replace the new static-only MobileTelemetryDefaults
namespace with an immutable key defined on the owning settings type or existing
shared settings contract. Update all references to use that owner, preserving
the exact sendAnonymousTelemetry key value and avoiding new ambient production
globals.

In `@web/app/`[locale]/(legal)/privacy-policy/page.tsx:
- Around line 98-105: Update the mobile analytics disclosure to accurately state
that telemetry is disabled by default and only collected after the user
explicitly enables the Send anonymous telemetry setting. Revise the paragraph in
the privacy policy page so it no longer claims analytics are sent unless
disabled, while retaining the opt-out and deletion-request details.

In `@web/app/api/account/route.ts`:
- Around line 639-659: Add a bounded timeout to the PostHog fetch in the
account-deletion flow, using an AbortController or equivalent AbortSignal and
ensuring the timer is cleaned up after completion. Preserve the existing non-OK
response handling and allow timeout errors to propagate through the existing
retryable-error path; update the fetch call associated with the account deletion
mutation before afterExternalMutation is invoked.
- Around line 661-690: postHogPersonDeletionConfig currently falls back to
POSTHOG_DEFAULT_ENVIRONMENT_ID for destructive deletions. Require a non-empty
explicit POSTHOG_ENVIRONMENT_ID or POSTHOG_PROJECT_ID, remove the default-ID
fallback, and preserve the existing error when neither is configured.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 933f7b62-61c4-4547-b14f-fe495552a471

📥 Commits

Reviewing files that changed from the base of the PR and between 1700d36 and fdc9b58.

📒 Files selected for processing (12)
  • Packages/iOS/CmuxMobileAnalytics/Sources/CmuxMobileAnalytics/AnalyticsConsentProviding.swift
  • Packages/iOS/CmuxMobileAnalytics/Tests/CmuxMobileAnalyticsTests/AnalyticsEmitterTests.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/MobileSettingsView.swift
  • ios/AppStoreReview/metadata-screenshots-checklist.md
  • ios/AppStoreReview/review-notes.md
  • ios/AppStoreReview/reviewer-setup.md
  • ios/cmux-ios.xcodeproj/project.pbxproj
  • ios/cmux/Resources/Localizable.xcstrings
  • ios/cmux/Resources/PrivacyInfo.xcprivacy
  • web/app/[locale]/(legal)/privacy-policy/page.tsx
  • web/app/api/account/route.ts
  • web/tests/account-route.test.ts

Comment thread ios/AppStoreReview/metadata-screenshots-checklist.md Outdated
Comment thread ios/AppStoreReview/reviewer-setup.md
Comment thread ios/cmux/Resources/PrivacyInfo.xcprivacy
Comment thread Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/MobileSettingsView.swift Outdated
Comment thread web/app/[locale]/(legal)/privacy-policy/page.tsx Outdated
Comment thread web/app/api/account/route.ts
Comment thread web/app/api/account/route.ts Outdated
Comment thread web/app/api/account/route.ts Outdated
…iew-readiness

# Conflicts:
#	web/app/[locale]/(legal)/privacy-policy/page.tsx
#	web/proxy.ts
Comment thread .github/workflows/ios-app-store.yml
Comment thread ios/AppStoreReview/metadata-screenshots-checklist.md
publish: @Sendable (AnalyticsConsentSnapshot) -> Void
) -> AnalyticsConsentSnapshot {
state.withCriticalRegion { state in
guard state.generation == base.generation else { return base }

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Stale consent admits post-revoke events

Medium Severity

When synchronize sees a generation mismatch, it returns the caller’s original base snapshot unchanged. If another thread already revoked consent, that stale snapshot can still have isEnabled true even though the fresh UserDefaults read was false. capture then only checks consent.isEnabled and enqueues the event into the AsyncStream after opt-out, which breaks the fail-closed “do not buffer while disabled” gate until the consumer later drops it via allows.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 9f3d8d5. Configure here.

@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 1 potential issue.

There are 2 total unresolved issues (including 1 from previous review).

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 c99ed1a. Configure here.

default:
true
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Duplicated crash reporting flag logic

Medium Severity

The CMUXCrashReportingEnabled Info.plist parser is copied in both AppCompositionRoot and MobileSettingsView. One copy decides whether Sentry is armed; the other decides whether Settings promises crash reports. If those parsers diverge later, the toggle label can advertise crash sharing while the composition root never starts reporting, or the reverse.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit c99ed1a. Configure here.

@azooz2003-bit
azooz2003-bit enabled auto-merge (squash) July 13, 2026 04:54

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

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

Inline comments:
In
`@Packages/iOS/CmuxMobileAnalytics/Tests/CmuxMobileAnalyticsTests/AnalyticsEmitterTests.swift`:
- Around line 44-62: Update the makeEmitter test helper to require an explicitly
injected NotificationCenter instead of defaulting to NotificationCenter.default.
Preserve existing call sites by passing their isolated local centers, and ensure
every invocation supplies a per-test NotificationCenter so observers cannot use
shared global state.

In
`@Packages/iOS/CmuxMobileCrashReporting/Sources/CmuxMobileCrashReporting/MobileCrashRevocationWatcher.swift`:
- Around line 46-57: Update the observer closure in MobileCrashRevocationWatcher
to capture self weakly and guard that it still exists before dispatching to
lifecycleQueue. Preserve the existing consent-change handling and state updates,
while ensuring the NotificationCenter observer cannot retain the watcher and
prevent deinit from removing it.

In
`@Packages/iOS/CmuxMobileCrashReporting/Tests/CmuxMobileCrashReportingTests/CrashTestCounter.swift`:
- Around line 17-19: Update CrashTestCounter.waitForValue to use a bounded
timeout while waiting for value to reach target, matching the test suite’s
existing DispatchSemaphore timeout pattern. Ensure the method exits or fails
clearly when the timeout expires instead of looping indefinitely.

In
`@Packages/iOS/CmuxMobileCrashReporting/Tests/CmuxMobileCrashReportingTests/CrashTestSequenceRecorder.swift`:
- Around line 21-23: Update CrashTestSequenceRecorder.waitForCount to use the
same bounded timeout behavior as CrashTestCounter.waitForValue. Ensure it fails
when the sequence does not reach the requested count within the timeout instead
of waiting indefinitely, while preserving the existing wait behavior when
progress is expected.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3ad51ba8-baf0-47c5-acfd-1a1e7186c313

📥 Commits

Reviewing files that changed from the base of the PR and between 1700d36 and c99ed1a.

📒 Files selected for processing (31)
  • .github/workflows/ios-app-store.yml
  • Packages/iOS/CmuxMobileAnalytics/Sources/CmuxMobileAnalytics/AnalyticsConsentGenerationGate.swift
  • Packages/iOS/CmuxMobileAnalytics/Sources/CmuxMobileAnalytics/AnalyticsConsentProviding.swift
  • Packages/iOS/CmuxMobileAnalytics/Sources/CmuxMobileAnalytics/AnalyticsConsentRevocationObserver.swift
  • Packages/iOS/CmuxMobileAnalytics/Sources/CmuxMobileAnalytics/AnalyticsConsentSnapshot.swift
  • Packages/iOS/CmuxMobileAnalytics/Sources/CmuxMobileAnalytics/AnalyticsCriticalState.swift
  • Packages/iOS/CmuxMobileAnalytics/Sources/CmuxMobileAnalytics/AnalyticsEmitter.swift
  • Packages/iOS/CmuxMobileAnalytics/Sources/CmuxMobileAnalytics/AnalyticsPendingEvent.swift
  • Packages/iOS/CmuxMobileAnalytics/Sources/CmuxMobileAnalytics/AnalyticsUploadStartGate.swift
  • Packages/iOS/CmuxMobileAnalytics/Sources/CmuxMobileAnalytics/AnalyticsUploadTaskRegistry.swift
  • Packages/iOS/CmuxMobileAnalytics/Sources/CmuxMobileAnalytics/AnalyticsUploading.swift
  • Packages/iOS/CmuxMobileAnalytics/Sources/CmuxMobileAnalytics/HTTPAnalyticsUploader.swift
  • Packages/iOS/CmuxMobileAnalytics/Tests/CmuxMobileAnalyticsTests/AnalyticsEmitterTests.swift
  • Packages/iOS/CmuxMobileAnalytics/Tests/CmuxMobileAnalyticsTests/AnalyticsUploadTaskRegistryTests.swift
  • Packages/iOS/CmuxMobileAnalytics/Tests/CmuxMobileAnalyticsTests/BlockingAnalyticsTokenProvider.swift
  • Packages/iOS/CmuxMobileAnalytics/Tests/CmuxMobileAnalyticsTests/BlockingAnalyticsUploader.swift
  • Packages/iOS/CmuxMobileAnalytics/Tests/CmuxMobileAnalyticsTests/ConsentAwareRecordingUploader.swift
  • Packages/iOS/CmuxMobileAnalytics/Tests/CmuxMobileAnalyticsTests/HTTPAnalyticsUploaderTests.swift
  • Packages/iOS/CmuxMobileAnalytics/Tests/CmuxMobileAnalyticsTests/TestGate.swift
  • Packages/iOS/CmuxMobileCrashReporting/Sources/CmuxMobileCrashReporting/CrashLifecycleAction.swift
  • Packages/iOS/CmuxMobileCrashReporting/Sources/CmuxMobileCrashReporting/MobileCrashReporter.swift
  • Packages/iOS/CmuxMobileCrashReporting/Sources/CmuxMobileCrashReporting/MobileCrashRevocationWatcher.swift
  • Packages/iOS/CmuxMobileCrashReporting/Sources/CmuxMobileCrashReporting/MobileCrashTransportSessionController.swift
  • Packages/iOS/CmuxMobileCrashReporting/Sources/CmuxMobileCrashReporting/MobileCrashTransportSessionControlling.swift
  • Packages/iOS/CmuxMobileCrashReporting/Sources/CmuxMobileCrashReporting/SentryCachePurger.swift
  • Packages/iOS/CmuxMobileCrashReporting/Tests/CmuxMobileCrashReportingTests/CrashTestCounter.swift
  • Packages/iOS/CmuxMobileCrashReporting/Tests/CmuxMobileCrashReportingTests/CrashTestSequenceRecorder.swift
  • Packages/iOS/CmuxMobileCrashReporting/Tests/CmuxMobileCrashReportingTests/CrashTestToggleConsent.swift
  • Packages/iOS/CmuxMobileCrashReporting/Tests/CmuxMobileCrashReportingTests/CrashTestTransportController.swift
  • Packages/iOS/CmuxMobileCrashReporting/Tests/CmuxMobileCrashReportingTests/MobileCrashReporterTests.swift
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/MobileSettingsView.swift

Comment on lines 44 to 62
private func makeEmitter(
uploader: RecordingAnalyticsUploader,
uploader: any AnalyticsUploading,
consent: (any AnalyticsConsentProviding)? = nil,
consentEnabled: Bool = true,
anonymousID: String = "anon-1",
flushBatchSize: Int = 50,
maxPendingEvents: Int = 1000
maxPendingEvents: Int = 1000,
notificationCenter: NotificationCenter = .default
) -> AnalyticsEmitter {
AnalyticsEmitter(
uploader: uploader,
consent: consent ?? FixedConsent(isTelemetryEnabled: consentEnabled),
anonymousID: anonymousID,
now: { Date(timeIntervalSince1970: 1_000_000) },
flushBatchSize: flushBatchSize,
maxPendingEvents: maxPendingEvents
maxPendingEvents: maxPendingEvents,
notificationCenter: notificationCenter
)
}

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

Avoid defaulting notificationCenter to the shared global instance in a test helper.

makeEmitter's new notificationCenter: NotificationCenter = .default parameter defaults to the process-wide shared center. Current call sites that need notification delivery already inject a local NotificationCenter(), so this isn't causing a bug today, but any future test that omits the parameter while still posting UserDefaults.didChangeNotification on .default — or any other concurrently-running suite doing the same — could leak into this emitter's observer for the life of the test.

As per path instructions, test code must "isolate shared static, global, UserDefaults, file, and related state per test."

♻️ Proposed fix
-        notificationCenter: NotificationCenter = .default
+        notificationCenter: NotificationCenter = NotificationCenter()
📝 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
private func makeEmitter(
uploader: RecordingAnalyticsUploader,
uploader: any AnalyticsUploading,
consent: (any AnalyticsConsentProviding)? = nil,
consentEnabled: Bool = true,
anonymousID: String = "anon-1",
flushBatchSize: Int = 50,
maxPendingEvents: Int = 1000
maxPendingEvents: Int = 1000,
notificationCenter: NotificationCenter = .default
) -> AnalyticsEmitter {
AnalyticsEmitter(
uploader: uploader,
consent: consent ?? FixedConsent(isTelemetryEnabled: consentEnabled),
anonymousID: anonymousID,
now: { Date(timeIntervalSince1970: 1_000_000) },
flushBatchSize: flushBatchSize,
maxPendingEvents: maxPendingEvents
maxPendingEvents: maxPendingEvents,
notificationCenter: notificationCenter
)
}
private func makeEmitter(
uploader: any AnalyticsUploading,
consent: (any AnalyticsConsentProviding)? = nil,
consentEnabled: Bool = true,
anonymousID: String = "anon-1",
flushBatchSize: Int = 50,
maxPendingEvents: Int = 1000,
notificationCenter: NotificationCenter = NotificationCenter()
) -> AnalyticsEmitter {
AnalyticsEmitter(
uploader: uploader,
consent: consent ?? FixedConsent(isTelemetryEnabled: consentEnabled),
anonymousID: anonymousID,
now: { Date(timeIntervalSince1970: 1_000_000) },
flushBatchSize: flushBatchSize,
maxPendingEvents: maxPendingEvents,
notificationCenter: notificationCenter
)
}
🤖 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/CmuxMobileAnalytics/Tests/CmuxMobileAnalyticsTests/AnalyticsEmitterTests.swift`
around lines 44 - 62, Update the makeEmitter test helper to require an
explicitly injected NotificationCenter instead of defaulting to
NotificationCenter.default. Preserve existing call sites by passing their
isolated local centers, and ensure every invocation supplies a per-test
NotificationCenter so observers cannot use shared global state.

Source: Path instructions

Comment on lines +46 to +57
token = notificationCenter.addObserver(
forName: UserDefaults.didChangeNotification,
object: nil,
queue: nil
) { _ in
self.lifecycleQueue.async { [self] in
let nextIsEnabled = consent.isTelemetryEnabled
guard nextIsEnabled != isEnabled else { return }
isEnabled = nextIsEnabled
(nextIsEnabled ? self.onEnable : self.onRevoke)?.body()
}
}

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

Observer closure retains self strongly, creating a cycle that makes deinit unreachable.

self.center = notificationCenter plus the strongly-captured self in the addObserver closure (line 50) and the [self] capture (line 51, still a strong capture, not weak) means center keeps the closure alive, which keeps self alive, which keeps center alive. deinit's removeObserver (lines 22-24) can therefore never run while the observer is installed. Harmless for the production .default singleton, but every test-owned NotificationCenter() + watcher pair (see MobileCrashReporterTests.swift) leaks for the rest of the process instead of being torn down as deinit implies it should be.

🔧 Proposed fix: capture weakly and guard
         token = notificationCenter.addObserver(
             forName: UserDefaults.didChangeNotification,
             object: nil,
             queue: nil
-        ) { _ in
-            self.lifecycleQueue.async { [self] in
-                let nextIsEnabled = consent.isTelemetryEnabled
-                guard nextIsEnabled != isEnabled else { return }
-                isEnabled = nextIsEnabled
-                (nextIsEnabled ? self.onEnable : self.onRevoke)?.body()
+        ) { [weak self] _ in
+            guard let self else { return }
+            self.lifecycleQueue.async { [weak self] in
+                guard let self else { return }
+                let nextIsEnabled = consent.isTelemetryEnabled
+                guard nextIsEnabled != self.isEnabled else { return }
+                self.isEnabled = nextIsEnabled
+                (nextIsEnabled ? self.onEnable : self.onRevoke)?.body()
             }
         }
📝 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
token = notificationCenter.addObserver(
forName: UserDefaults.didChangeNotification,
object: nil,
queue: nil
) { _ in
self.lifecycleQueue.async { [self] in
let nextIsEnabled = consent.isTelemetryEnabled
guard nextIsEnabled != isEnabled else { return }
isEnabled = nextIsEnabled
(nextIsEnabled ? self.onEnable : self.onRevoke)?.body()
}
}
token = notificationCenter.addObserver(
forName: UserDefaults.didChangeNotification,
object: nil,
queue: nil
) { [weak self] _ in
guard let self else { return }
self.lifecycleQueue.async { [weak self] in
guard let self else { return }
let nextIsEnabled = consent.isTelemetryEnabled
guard nextIsEnabled != self.isEnabled else { return }
self.isEnabled = nextIsEnabled
(nextIsEnabled ? self.onEnable : self.onRevoke)?.body()
}
}
🤖 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/CmuxMobileCrashReporting/Sources/CmuxMobileCrashReporting/MobileCrashRevocationWatcher.swift`
around lines 46 - 57, Update the observer closure in
MobileCrashRevocationWatcher to capture self weakly and guard that it still
exists before dispatching to lifecycleQueue. Preserve the existing
consent-change handling and state updates, while ensuring the NotificationCenter
observer cannot retain the watcher and prevent deinit from removing it.

Comment on lines +17 to +19
func waitForValue(_ target: Int) {
while value < target { changed.wait() }
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Add a bounded timeout to waitForValue to avoid indefinite test hangs.

Unlike the other DispatchSemaphore.wait(timeout:) calls elsewhere in this test suite, this loop blocks forever if the target count is never reached (e.g. on a real regression), instead of failing fast with a clear timeout.

♻️ Suggested bounded wait
-    func waitForValue(_ target: Int) {
-        while value < target { changed.wait() }
-    }
+    `@discardableResult`
+    func waitForValue(_ target: Int, timeout: TimeInterval = 5) -> Bool {
+        let deadline = DispatchTime.now() + timeout
+        while value < target {
+            if changed.wait(timeout: deadline) == .timedOut { return value >= target }
+        }
+        return true
+    }
📝 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
func waitForValue(_ target: Int) {
while value < target { changed.wait() }
}
`@discardableResult`
func waitForValue(_ target: Int, timeout: TimeInterval = 5) -> Bool {
let deadline = DispatchTime.now() + timeout
while value < target {
if changed.wait(timeout: deadline) == .timedOut { return value >= target }
}
return true
}
🤖 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/CmuxMobileCrashReporting/Tests/CmuxMobileCrashReportingTests/CrashTestCounter.swift`
around lines 17 - 19, Update CrashTestCounter.waitForValue to use a bounded
timeout while waiting for value to reach target, matching the test suite’s
existing DispatchSemaphore timeout pattern. Ensure the method exits or fails
clearly when the timeout expires instead of looping indefinitely.

Comment on lines +21 to +23
func waitForCount(_ count: Int) {
while sequence.count < count { changed.wait() }
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Same unbounded-wait concern as CrashTestCounter.waitForValue.

waitForCount blocks forever if the expected sequence never arrives, instead of failing with a timeout. Same fix applies here.

♻️ Suggested bounded wait
-    func waitForCount(_ count: Int) {
-        while sequence.count < count { changed.wait() }
-    }
+    `@discardableResult`
+    func waitForCount(_ count: Int, timeout: TimeInterval = 5) -> Bool {
+        let deadline = DispatchTime.now() + timeout
+        while sequence.count < count {
+            if changed.wait(timeout: deadline) == .timedOut { return sequence.count >= count }
+        }
+        return true
+    }
📝 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
func waitForCount(_ count: Int) {
while sequence.count < count { changed.wait() }
}
`@discardableResult`
func waitForCount(_ count: Int, timeout: TimeInterval = 5) -> Bool {
let deadline = DispatchTime.now() + timeout
while sequence.count < count {
if changed.wait(timeout: deadline) == .timedOut { return sequence.count >= count }
}
return true
}
🤖 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/CmuxMobileCrashReporting/Tests/CmuxMobileCrashReportingTests/CrashTestSequenceRecorder.swift`
around lines 21 - 23, Update CrashTestSequenceRecorder.waitForCount to use the
same bounded timeout behavior as CrashTestCounter.waitForValue. Ensure it fails
when the sequence does not reach the requested count within the timeout instead
of waiting indefinitely, while preserving the existing wait behavior when
progress is expected.

@azooz2003-bit
azooz2003-bit merged commit fa8e326 into main Jul 13, 2026
55 of 61 checks passed
azooz2003-bit pushed a commit that referenced this pull request Jul 15, 2026
Lands main's dev-instance pairing isolation so the phone can tell tagged
dev builds apart from production and each other (instance tags carried
through pairing, routes, authority checks, and computer labels) — the
root cause behind the sim silently rendering another instance's
workspaces, including production's.

This branch's v1 agent-chat deletion stays authoritative: main's interim
chat fixes and the artifact-viewing UI built on the deleted chat package
(#7862-era artifact chips, gallery, sheets, and their tests) are removed
rather than restored; artifact viewing is a recorded parity item to
reintegrate as transcript activity-rail content in the new GUI.
ProcessSnapshotCentralizationTests keeps its compatibility test with the
chat-registry-dependent test and helper actors stripped.
AgentProcessObservationSource adapts to main's actor-backed process
snapshot store, preserving exact-basename detection semantics. Composer
hosting keeps this branch's judged implementation (nothing to port from
main's in-file variant). Full localization-catalog union; budget,
package-group, resolved-policy, and test-wiring gates green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

This branch was successfully deployed

1 active deployment
Preview – cmux — 2b65cd61 Deployed Jul 13, 2026 by vercel[bot]
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