Skip to content

Add iOS App Store production lane - #7644

Merged
azooz2003-bit merged 9 commits into
mainfrom
feat-ios-app-store-lane
Jul 9, 2026
Merged

azooz2003-bit merged 9 commits into
mainfrom
feat-ios-app-store-lane

Conversation

@azooz2003-bit

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

Copy link
Copy Markdown
Collaborator

Summary

  • add a production iOS App Store upload lane for com.cmuxterm.app without changing the beta default
  • add App Store review notes, metadata/screenshots checklist, ASC workflow, and validation script
  • gate app-embedded billing in App Store mode so Stripe/Stack checkout and billing-management links are not exposed

Testing

  • bash -n ios/scripts/upload-testflight.sh ios/scripts/upload-app-store.sh ios/scripts/validate-app-store-release.sh
  • asc workflow validate
  • asc workflow run --dry-run ios-app-store-validate APP_ID:com.cmuxterm.app VERSION:1.0.1 STRICT_VALIDATE:1
  • python3 -m py_compile ios/scripts/asc_max_build.py ios/scripts/asc_set_testflight_notes.py ios/scripts/asc_assign_external_testflight_group.py
  • bun test ./tests/app-pricing-page.test.tsx ./tests/billing-checkout-route.test.ts
  • bun run typecheck
  • git diff --check

Issues

  • None

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


Note

Medium Risk
Touches release automation (signing, upload, optional App Review submit) and billing/checkout behavior for App Store distribution; mistakes could block releases or affect payment flows in the iOS webview.

Overview
Adds a production iOS App Store path for com.cmuxterm.app alongside the existing TestFlight beta lane: --lane appstore (via upload-app-store.sh), stricter IPA checks (aps-environment, Sign in with Apple, no Founders Edition links), manual CI workflow with optional metadata staging and guarded review submit, plus validate-app-store-release.sh, .asc workflows, and ios/AppStoreReview/ notes/checklists.

App Store compliance removes the Mac setup help “Download cmux” external link (with tests), gates embedded web billing when cmux_distribution=appstore (disabled CTAs on /app-pricing, checkout redirect before Stripe/Stack), and adds localized billingUnavailable copy.

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


Summary by cubic

Adds a production iOS App Store lane for com.cmuxterm.app with end-to-end App Store Connect validation and optional submission. Also blocks external purchase links for App Store builds across the app and embedded web, with CI/IPA checks to enforce it.

  • New Features

    • Production lane: --lane appstore in ios/scripts/upload-testflight.sh (via ios/scripts/upload-app-store.sh) sets bundle id/display name/profile, verifies the archive’s bundle id, enforces aps-environment=production and com.apple.developer.applesignin=Default during re-sign, disables --external/--auto-version, skips TestFlight notes/group assignment, and scans the IPA for “Founders Edition” links.
    • CI workflow “iOS App Store (production)” builds with manual signing, uploads, waits for processing, validates, and can optionally stage metadata or submit; gated to main, serialized, validates secrets and provisioning profile, and outputs the final build number.
    • ASC automation: .asc/workflow.json and ios/scripts/validate-app-store-release.sh run readiness checks (asc validate), validate metadata/screenshots, support --wait-build, --stage-dry-run, --submit-dry-run, and guarded submit with --confirm.
    • Review package at ios/AppStoreReview/ with reviewer notes and a metadata/screenshots checklist.
    • App Store compliance: setup help removes external purchase/enrollment links (tests added). Web gating hides Pro/Team/Enterprise CTAs and Stripe portal on /app-pricing, and /api/billing/checkout redirects when cmux_distribution=appstore (or cmux_ios_app_store=1); tests cover both surfaces. Localized the “billing unavailable” message across supported languages.
  • Migration

    • Add repo variable IOS_APPSTORE_APP_ID and secret IOS_APPSTORE_PROVISIONING_PROFILE_BASE64 (profile for com.cmuxterm.app, with aps-environment=production and com.apple.developer.applesignin=Default).
    • Run the manual workflow “iOS App Store (production)” and enable submission only when the checklist is complete.
    • iOS webviews should pass cmux_app=1&cmux_distribution=appstore to enforce billing gating.

Written for commit 411ff15. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features
    • Added App Store–distribution “production lane” automation for upload, validation, and optional App Review submission, plus support for stage/submit dry runs.
    • App Store mode now gates billing: checkout routes redirect to pricing and CTAs are disabled.
  • Documentation
    • Added production-lane guidance, submission checklist, and reviewer notes for App Store readiness.
  • Bug Fixes
    • Improved App Store upload/validation safety and stricter lane/entitlement checks.
  • Tests
    • Expanded coverage for App Store-mode pricing/checkout blocking and mobile setup help-gate link behavior.

@vercel

vercel Bot commented Jul 8, 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 9, 2026 2:42am
cmux-staging Building Building Preview, Comment Jul 9, 2026 2:42am

@coderabbitai

coderabbitai Bot commented Jul 8, 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

This PR adds iOS App Store production submission tooling and documentation, updates iOS upload scripts for an appstore lane, and blocks web billing checkout and pricing actions in App Store distribution mode.

Changes

iOS App Store production lane

Layer / File(s) Summary
App Store readiness validation script
ios/scripts/validate-app-store-release.sh
Adds release validation, staging preview, and submission preview flows for App Store builds.
App Store upload script and appstore lane
ios/scripts/upload-app-store.sh, ios/scripts/upload-testflight.sh
Adds an appstore wrapper/lane and keeps TestFlight-specific behavior on beta.
GitHub Actions workflows for App Store validation and production upload
.asc/workflow.json, .github/workflows/ios-app-store.yml
Adds ASC validation workflows and a manual production workflow that signs, uploads, validates, and optionally submits an App Store build.
App Store review docs and README updates
ios/AppStoreReview/*, ios/README.md
Adds review notes, metadata/screenshots checklist, and production-lane documentation with the expected scripts and CI inputs.
Setup help link removal
Packages/iOS/CmuxMobileShellUI/.../SetupHelpGateContent.swift, Packages/iOS/CmuxMobileShellUI/Tests/.../SetupHelpGateContentTests.swift
Removes the setup download link for the never-paired state and updates tests to expect no external download link.

Web billing/pricing App Store gating

Layer / File(s) Summary
Billing checkout redirect
web/app/lib/billing.ts, web/app/api/billing/checkout/route.ts, web/tests/billing-checkout-route.test.ts
Detects App Store distribution mode, redirects checkout requests to pricing, and skips billing session creation.
Pricing page CTA gating
web/app/app-pricing/page.tsx, web/tests/app-pricing-page.test.tsx
Uses shared App Store distribution detection to disable checkout and billing-management CTAs on the pricing page.
Billing unavailable translations
web/messages/*.json
Adds localized pricing.billingUnavailable copy for the new disabled billing state.

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

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)

Check name Status Explanation Resolution
Cmux Full Internationalization ❌ Error German billingUnavailable uses informal “versuche” instead of the repo’s formal “Sie” convention, so the new locale entry is inconsistent. Update web/messages/de.json to formal German copy, e.g. “Bitte versuchen Sie es später erneut.”, then re-run i18n validation.
✅ Passed checks (24 passed)
Check name Status Explanation
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Cmux Swift Actor Isolation ✅ Passed Touched Swift code is a pure top-level value model plus tests; no new implicit MainActor, Sendable, or UI-store isolation regression was introduced.
Cmux Swift Blocking Runtime ✅ Passed Touched Swift code is pure content/data plus tests; no semaphores, sleeps, sync waits, polling, or locks were introduced.
Cmux Browser Automation Off-Main ✅ Passed PR only changes iOS/App Store and web billing/docs; it does not touch the browser-automation files covered by the off-main rule.
Cmux Expensive Synchronous Load ✅ Passed The only Swift production change is static setup-help text/link removal; no synchronous agent-history load or main-actor/interactive parsing was added.
Cmux Cache Substitution Correctness ✅ Passed The PR only adds App Store gating/UI and docs; the snapshot path still reads Stack fresh via getUser, with no cached/opportunistic substitution.
Cmux No Hacky Sleeps ✅ Passed No literal sleeps/timers/polling were added; the only wait is asc builds wait in validate-app-store-release.sh, and workflow YAML is out of scope.
Cmux Algorithmic Complexity ✅ Passed No new scalable nested scans or hot-path sorting/filtering; only fixed-size UI lists and single-IPA checks were added.
Cmux Swift Concurrency ✅ Passed Only a pure content mapping and synchronous tests changed; no new DispatchQueue, Combine, completion handlers, or fire-and-forget Tasks were added.
Cmux Swift @Concurrent ✅ Passed Touched Swift files are pure UI content/tests; no new or changed async, nonisolated, or @concurrent code appears, so the rule isn’t violated.
Cmux Swift File And Package Boundaries ✅ Passed PASS: The only production Swift change is a small 71-line package UI glue file; it just removes an external link and adds a focused test, with no boundary violation.
Cmux Swiftpm Lockfiles ✅ Passed Diff has no Package.swift, Package.resolved, .gitignore, or Xcode package-reference changes, so the SwiftPM lockfile rule isn’t violated.
Cmux Swift Logging ✅ Passed PASS: The only Swift diff removed a help-link constant; no print, NSLog, dump, or Logger additions or changes in touched Swift code.
Cmux User-Facing Error Privacy ✅ Passed User-facing billing copy stays generic; vendor/id references are confined to docs and CI/ops scripts, which are allowed.
Cmux Swiftui State Layout ✅ Passed No changed SwiftUI state/layout patterns: the only touched Swift file is a pure data struct, plus tests; no ObservableObject, @Published, GeometryReader, or lazy-row store refs.
Cmux Architecture Rethink ✅ Passed The Swift change is a local content fix: it removes one external link from SetupHelpGateContent and adds tests; no timing hacks, extra owners, or split lifecycle wiring.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed Only setup-help content/tests changed; no NSWindow/NSPanel/WindowGroup or cmuxAuxiliaryWindowIdentifiers code was added or altered, so the rule doesn’t apply.
Cmux Source Artifacts ✅ Passed All changed paths are intentional source files, configs, docs, tests, or localization catalogs; none are logs, caches, build output, temp, or other stray artifacts.
Cmux No Test Or Debug Seam In Production Source ✅ Passed HEAD changes only touched workflows, scripts, JSON, and docs; no Swift files under a production Sources/ path were modified.
Cmux No Ambient Global State ✅ Passed PASS: the only production Swift change is a regular struct factory in SetupHelpGateContent; the PR removes a file-scope URL constant and adds no new global state/singletons.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding an iOS App Store production lane.
✨ 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 feat-ios-app-store-lane

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

Copy link
Copy Markdown
Contributor

Greptile Summary

Adds a production iOS App Store lane (com.cmuxterm.app) alongside the existing TestFlight beta path, with App Store compliance gating that blocks external purchase and enrollment links from being exposed in App Store distribution mode.

  • New production lane & CI: upload-app-store.sh wraps upload-testflight.sh --lane appstore; a manual-only .github/workflows/ios-app-store.yml archives, uploads, waits for ASC processing, validates, and optionally submits — all gated to main and serialized with cancel-in-progress: false.
  • App Store compliance gating: isAppStoreDistributionMode is centralized in billing.ts and consumed by both /app-pricing/page.tsx and /api/billing/checkout/route.ts; when cmux_distribution=appstore, checkout/portal/enterprise CTAs are disabled and the checkout route redirects before Stack or Stripe touch.
  • Internationalization: pricing.billingUnavailable is added to all 22 non-English locale files, and the new tests harden both the pricing page gating and the checkout redirect.

Confidence Score: 5/5

Safe to merge. App Store compliance gating is correctly implemented and validated by new tests across both the billing checkout redirect and the pricing page CTA surfaces.

The App Store detection function is centralized in billing.ts and consistently consumed by both the checkout route and the pricing page. The upload script correctly gates --external and --auto-version for the appstore lane, fixes a pre-existing set -e edge case in entitlement verification, and adds an IPA scan for founders-edition strings. The CI workflow is manual-only, serialized, and validates provisioning profile entitlements before any upload.

No files require special attention.

Important Files Changed

Filename Overview
web/app/lib/billing.ts Adds isAppStoreDistributionMode() and firstSearchParam() helpers; detection is correctly centralized and consumed by both the pricing page and checkout route, resolving the prior duplication concern.
web/app/api/billing/checkout/route.ts App Store gate redirects before Stack/Stripe initialization; stackServerApp is now lazily loaded via dynamic import, avoiding module-level initialization for gated requests. Clean refactor.
web/app/app-pricing/page.tsx Correctly disables Pro/Team/Enterprise CTAs and Stripe portal link in App Store mode; uses the shared isAppStoreDistributionMode() from billing.ts with proper ternary branching.
ios/scripts/upload-testflight.sh Adds appstore lane with correct mutual-exclusion guards for --external and --auto-version; also fixes a subtle set -e bug in verify_ipa_aps_environment_production by using
ios/scripts/validate-app-store-release.sh New read-only validation script; uses asc CLI as the real signal source for build processing. Screenshot device-type deduplication on first explicit arg is correctly handled.
.github/workflows/ios-app-store.yml Manual-only workflow gated to main; serialized with cancel-in-progress: false; validates provisioning profile entitlements before upload. Keychain cleanup is properly gated with if: always().
Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/SetupHelpGateContent.swift Removes Founders Edition external URL and nil-s the link for the signedInNeverPaired gate; covered by the new SetupHelpGateContentTests.
ios/scripts/upload-app-store.sh Thin wrapper that guards against accidentally passing --lane and always delegates to upload-testflight.sh --lane appstore.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant iOS as iOS App
    participant WEB as /api/billing/checkout
    participant Pricing as /app-pricing
    participant Stack as Stack/Stripe

    iOS->>WEB: "GET ?plan=pro&cmux_distribution=appstore"
    WEB->>WEB: isAppStoreDistributionMode() true
    WEB-->>iOS: "307 redirect to /app-pricing?billing=unavailable"
    Note over Stack: Stack/Stripe never called

    iOS->>Pricing: "GET ?cmux_app=1&cmux_distribution=appstore"
    Pricing->>Pricing: "appStorePaymentGated = true"
    Pricing-->>iOS: DisabledButton for all CTAs

    iOS->>Pricing: "GET ?cmux_app=1"
    Pricing->>Pricing: "appStorePaymentGated = false"
    Pricing-->>iOS: PrimaryLink to checkout
    iOS->>WEB: "GET ?plan=pro"
    WEB->>Stack: checkoutStackServerApp() proceed
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant iOS as iOS App
    participant WEB as /api/billing/checkout
    participant Pricing as /app-pricing
    participant Stack as Stack/Stripe

    iOS->>WEB: "GET ?plan=pro&cmux_distribution=appstore"
    WEB->>WEB: isAppStoreDistributionMode() true
    WEB-->>iOS: "307 redirect to /app-pricing?billing=unavailable"
    Note over Stack: Stack/Stripe never called

    iOS->>Pricing: "GET ?cmux_app=1&cmux_distribution=appstore"
    Pricing->>Pricing: "appStorePaymentGated = true"
    Pricing-->>iOS: DisabledButton for all CTAs

    iOS->>Pricing: "GET ?cmux_app=1"
    Pricing->>Pricing: "appStorePaymentGated = false"
    Pricing-->>iOS: PrimaryLink to checkout
    iOS->>WEB: "GET ?plan=pro"
    WEB->>Stack: checkoutStackServerApp() proceed
Loading

Reviews (7): Last reviewed commit: "Localize App Store billing unavailable c..." | Re-trigger Greptile

SCREENSHOTS_DIR="${IOS_APPSTORE_SCREENSHOTS_DIR:-$IOS_DIR/AppStoreReview/screenshots}"
REVIEW_NOTES="$IOS_DIR/AppStoreReview/review-notes.md"
CHECKLIST="$IOS_DIR/AppStoreReview/metadata-screenshots-checklist.md"
SCREENSHOT_DEVICE_TYPES=(IPHONE_65 IPAD_PRO_3GEN_129)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 --screenshot-device-type appends to the pre-populated default list rather than replacing it. If either default device type (IPHONE_65 or IPAD_PRO_3GEN_129) is passed explicitly, that device type gets validated twice, producing duplicate output. Clearing the defaults on first explicit use would match expected CLI behaviour.

Suggested change
SCREENSHOT_DEVICE_TYPES=(IPHONE_65 IPAD_PRO_3GEN_129)
SCREENSHOT_DEVICE_TYPES=(IPHONE_65 IPAD_PRO_3GEN_129)
_SCREENSHOT_DEVICE_TYPES_EXPLICIT=0

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

--wait-build) WAIT_BUILD=1; shift ;;
--metadata-dir) METADATA_DIR="${2:-}"; shift 2 ;;
--screenshots-dir) SCREENSHOTS_DIR="${2:-}"; shift 2 ;;
--screenshot-device-type) SCREENSHOT_DEVICE_TYPES+=("${2:-}"); shift 2 ;;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 With the companion initialisation above, reset the defaults on the first explicit --screenshot-device-type argument so that passing a specific type doesn't duplicate validation of any pre-populated default.

Suggested change
--screenshot-device-type) SCREENSHOT_DEVICE_TYPES+=("${2:-}"); shift 2 ;;
--screenshot-device-type) [[ "$_SCREENSHOT_DEVICE_TYPES_EXPLICIT" -eq 0 ]] && SCREENSHOT_DEVICE_TYPES=(); _SCREENSHOT_DEVICE_TYPES_EXPLICIT=1; SCREENSHOT_DEVICE_TYPES+=("${2:-}"); shift 2 ;;

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

@azooz2003-bit
azooz2003-bit force-pushed the feat-ios-app-store-lane branch from df37eef to bdc7b60 Compare July 8, 2026 20:03

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

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

Inline comments:
In @.asc/workflow.json:
- Around line 11-13: The readiness workflow step is missing the wait-for-build
behavior, which can race App Store Connect ingestion. Update the `readiness`
command in the workflow to pass `--wait-build` to
`ios/scripts/validate-app-store-release.sh`, matching the usage in the README
and `ios-app-store.yml`. Keep the existing `--app`, `--version`,
`--build-number`, and `--strict` arguments intact while adding the new flag.

In @.github/workflows/ios-app-store.yml:
- Line 40: The job-level env entry for CMUX_BUILD_NUMBER_OUT_FILE uses
runner.temp, but the runner context is not available there. Move this variable
out of the top-level env and define it on the upload step instead, where
runner.temp is valid, so the build-number handoff used by the upload flow
resolves correctly.

In `@web/app/api/billing/checkout/route.ts`:
- Around line 311-333: The app-store distribution check is duplicated between
appStoreDistributionMode in the checkout route and the pricing page, so extract
the shared detection logic into a common helper under web/lib/billing. Refactor
the core condition to accept a generic key lookup function (instead of
NextRequest or Record directly), then have both appStoreDistributionMode call
sites delegate to that helper so the cmux_distribution and cmux_ios_app_store
rules stay in sync.
🪄 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: 19eaf8a8-2776-4f85-962f-dce1caba4261

📥 Commits

Reviewing files that changed from the base of the PR and between d65d918 and df37eef.

📒 Files selected for processing (13)
  • .asc/workflow.json
  • .github/workflows/ios-app-store.yml
  • ios/AppStoreReview/README.md
  • ios/AppStoreReview/metadata-screenshots-checklist.md
  • ios/AppStoreReview/review-notes.md
  • ios/README.md
  • ios/scripts/upload-app-store.sh
  • ios/scripts/upload-testflight.sh
  • ios/scripts/validate-app-store-release.sh
  • web/app/api/billing/checkout/route.ts
  • web/app/app-pricing/page.tsx
  • web/tests/app-pricing-page.test.tsx
  • web/tests/billing-checkout-route.test.ts

Comment thread .asc/workflow.json
Comment thread .github/workflows/ios-app-store.yml Outdated
Comment thread web/app/api/billing/checkout/route.ts Outdated
@azooz2003-bit
azooz2003-bit force-pushed the feat-ios-app-store-lane branch from bdc7b60 to 985aa87 Compare July 8, 2026 20:10

@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/app-pricing/page.tsx (1)

118-132: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

billingExternal message is misleading for App Store users with Stripe-managed billing.

When snapshot.isPro is true, billingManagement === "stripe", and appStorePaymentGated is true, the condition snapshot.billingManagement === "stripe" && !appStorePaymentGated evaluates to false, so the UI falls through to pricing.billingExternal ("Your subscription is managed by our previous billing system. Contact support to make changes."). For a Pro user whose subscription is actively managed by Stripe, this is factually incorrect — it implies a legacy/external system they don't use.

Consider adding a dedicated App Store billing-management message (e.g., "Billing management is not available in this mode. Contact support to make changes.") with entries in all supported locales, or reusing billingUnavailable if the "try again later" phrasing is acceptable.

💡 Suggested approach

Add a new key to web/messages/en.json and web/messages/ja.json:

"billingAppStoreManaged": "Billing management is not available in this mode. Contact support to make changes."

Then update the Pro card condition:

               {snapshot.billingManagement === "stripe" && !appStorePaymentGated ? (
                 <SecondaryLink href="/api/billing/portal">
                   {pricing.manageBilling}
                 </SecondaryLink>
               ) : appStorePaymentGated ? (
-                <p className="text-sm leading-6 text-muted">
-                  {pricing.billingExternal}
-                </p>
+                <p className="text-sm leading-6 text-muted">
+                  {pricing.billingAppStoreManaged}
+                </p>
               ) : (
                 <p className="text-sm leading-6 text-muted">
                   {pricing.billingExternal}
                 </p>
               )}
🤖 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/app-pricing/page.tsx` around lines 118 - 132, The Pro billing state
in the pricing page is falling through to the legacy `pricing.billingExternal`
copy when `snapshot.isPro`, `snapshot.billingManagement === "stripe"`, and
`appStorePaymentGated` are all true, which misstates the user’s billing setup.
Update the conditional in `app-pricing/page.tsx` so App Store-gated
Stripe-managed users render a dedicated App Store billing message instead of
`billingExternal`, and add the new localized message key to the message files
used by `pricing` (for example `en.json` and `ja.json`). Keep the existing
`manageBilling` and `billingUnavailable` paths unchanged for their current
cases.
♻️ Duplicate comments (1)
.github/workflows/ios-app-store.yml (1)

40-40: 🩺 Stability & Availability | 🔴 Critical

Fix: runner context is not available in job-level env blocks.

${{ runner.temp }} on line 40 is evaluated in the job-level env block, where the runner context is not available (only github, inputs, matrix, needs, secrets, strategy, vars are). This causes CMUX_BUILD_NUMBER_OUT_FILE to resolve to an incorrect path, breaking the upload step's build-number handoff. Move it to the step-level env of the upload step where runner.temp is valid.

🔧 Proposed fix: move env var to step level
       ASC_APP_ID: ${{ vars.IOS_APPSTORE_APP_ID }}
       INPUT_BUILD_NUMBER: ${{ github.event.inputs.build_number }}
       INPUT_COPY_METADATA_FROM: ${{ github.event.inputs.copy_metadata_from }}
       INPUT_SUBMIT_FOR_REVIEW: ${{ github.event.inputs.submit_for_review }}
-      CMUX_BUILD_NUMBER_OUT_FILE: ${{ runner.temp }}/cmux-final-appstore-build-number.txt
     steps:

And add step-level env to the upload step:

       - name: Archive, export, and upload production build
         id: upload
+        env:
+          CMUX_BUILD_NUMBER_OUT_FILE: ${{ runner.temp }}/cmux-final-appstore-build-number.txt
         run: |
           set -euo pipefail
           ARGS=(--signing manual)
🤖 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 @.github/workflows/ios-app-store.yml at line 40, The job-level env in
ios-app-store workflow is using runner.temp for CMUX_BUILD_NUMBER_OUT_FILE, but
the runner context is not available there. Move CMUX_BUILD_NUMBER_OUT_FILE out
of the job-wide env block and set it on the specific upload step’s step-level
env instead, where runner.temp is valid, so the build-number handoff path
resolves correctly.

Source: Linters/SAST tools

🤖 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/app-pricing/page.tsx`:
- Around line 118-132: The Pro billing state in the pricing page is falling
through to the legacy `pricing.billingExternal` copy when `snapshot.isPro`,
`snapshot.billingManagement === "stripe"`, and `appStorePaymentGated` are all
true, which misstates the user’s billing setup. Update the conditional in
`app-pricing/page.tsx` so App Store-gated Stripe-managed users render a
dedicated App Store billing message instead of `billingExternal`, and add the
new localized message key to the message files used by `pricing` (for example
`en.json` and `ja.json`). Keep the existing `manageBilling` and
`billingUnavailable` paths unchanged for their current cases.

---

Duplicate comments:
In @.github/workflows/ios-app-store.yml:
- Line 40: The job-level env in ios-app-store workflow is using runner.temp for
CMUX_BUILD_NUMBER_OUT_FILE, but the runner context is not available there. Move
CMUX_BUILD_NUMBER_OUT_FILE out of the job-wide env block and set it on the
specific upload step’s step-level env instead, where runner.temp is valid, so
the build-number handoff path resolves correctly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 41776c9d-5b59-4b0f-aa79-23409ac7cfcd

📥 Commits

Reviewing files that changed from the base of the PR and between df37eef and 985aa87.

📒 Files selected for processing (14)
  • .asc/workflow.json
  • .github/workflows/ios-app-store.yml
  • ios/AppStoreReview/README.md
  • ios/AppStoreReview/metadata-screenshots-checklist.md
  • ios/AppStoreReview/review-notes.md
  • ios/README.md
  • ios/scripts/upload-app-store.sh
  • ios/scripts/upload-testflight.sh
  • ios/scripts/validate-app-store-release.sh
  • web/app/api/billing/checkout/route.ts
  • web/app/app-pricing/page.tsx
  • web/app/lib/billing.ts
  • web/tests/app-pricing-page.test.tsx
  • web/tests/billing-checkout-route.test.ts

Comment thread ios/scripts/validate-app-store-release.sh
Comment thread web/app/app-pricing/page.tsx

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

♻️ Duplicate comments (1)
.asc/workflow.json (1)

11-13: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

--wait-build is missing from all three workflow steps

The readiness step (Line 12) was previously flagged for missing --wait-build and marked as addressed in commit bdc7b60, but the current code does not include it. The stage-dry-run (Line 24) and submit-dry-run (Line 36) steps also lack it.

When --build-number is provided, validate-app-store-release.sh calls asc builds info to resolve the build ID. Without --wait-build, this skips asc builds wait and can race ASC ingestion — causing intermittent failures if the build hasn't finished processing. The README and ios-app-store.yml reportedly already use --wait-build; these workflow definitions should match.

🔧 Proposed fix: add conditional --wait-build to readiness step
         {
           "name": "readiness",
-          "run": "set -- --app \"$APP_ID\" --version \"$VERSION\"; if [ -n \"${BUILD_NUMBER:-}\" ]; then set -- \"$@\" --build-number \"$BUILD_NUMBER\"; fi; if [ -n \"${STRICT_VALIDATE:-}\" ]; then set -- \"$@\" --strict; fi; ios/scripts/validate-app-store-release.sh \"$@\""
+          "run": "set -- --app \"$APP_ID\" --version \"$VERSION\"; if [ -n \"${BUILD_NUMBER:-}\" ]; then set -- \"$@\" --build-number \"$BUILD_NUMBER\" --wait-build; fi; if [ -n \"${STRICT_VALIDATE:-}\" ]; then set -- \"$@\" --strict; fi; ios/scripts/validate-app-store-release.sh \"$@\""
         }

For the stage-dry-run and submit-dry-run steps, add --wait-build alongside --build-number:

-          "run": "ios/scripts/validate-app-store-release.sh --app \"$APP_ID\" --version \"$VERSION\" --build-number \"$BUILD_NUMBER\" --copy-metadata-from \"$COPY_METADATA_FROM\" --stage-dry-run ${STRICT_VALIDATE:+--strict}"
+          "run": "ios/scripts/validate-app-store-release.sh --app \"$APP_ID\" --version \"$VERSION\" --build-number \"$BUILD_NUMBER\" --wait-build --copy-metadata-from \"$COPY_METADATA_FROM\" --stage-dry-run ${STRICT_VALIDATE:+--strict}"
-          "run": "ios/scripts/validate-app-store-release.sh --app \"$APP_ID\" --version \"$VERSION\" --build-number \"$BUILD_NUMBER\" --submit-dry-run ${STRICT_VALIDATE:+--strict}"
+          "run": "ios/scripts/validate-app-store-release.sh --app \"$APP_ID\" --version \"$VERSION\" --build-number \"$BUILD_NUMBER\" --wait-build --submit-dry-run ${STRICT_VALIDATE:+--strict}"

Run the following script to verify --wait-build usage across the codebase:

#!/bin/bash
# Description: Verify --wait-build usage in README, ios-app-store.yml, and workflow.json
rg -n --context 2 'wait-build|wait_build|WAIT_BUILD' ios/AppStoreReview/README.md .github/workflows/ios-app-store.yml .asc/workflow.json ios/scripts/validate-app-store-release.sh

Also applies to: 23-24, 35-36

🤖 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 @.asc/workflow.json around lines 11 - 13, The workflow step commands for
readiness, stage-dry-run, and submit-dry-run are missing the conditional
--wait-build flag, so update each step’s run command in workflow.json to pass
--wait-build whenever --build-number is set. Keep the existing argument-building
pattern in place, and align these steps with the behavior already used by
ios/scripts/validate-app-store-release.sh and the documented ios-app-store
workflow so build ingestion is waited on before resolving the build ID.
🤖 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.

Duplicate comments:
In @.asc/workflow.json:
- Around line 11-13: The workflow step commands for readiness, stage-dry-run,
and submit-dry-run are missing the conditional --wait-build flag, so update each
step’s run command in workflow.json to pass --wait-build whenever --build-number
is set. Keep the existing argument-building pattern in place, and align these
steps with the behavior already used by
ios/scripts/validate-app-store-release.sh and the documented ios-app-store
workflow so build ingestion is waited on before resolving the build ID.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 613fd351-7e6b-41be-954b-ddff45fc84ed

📥 Commits

Reviewing files that changed from the base of the PR and between 5abff07 and 55aebc9.

📒 Files selected for processing (1)
  • .asc/workflow.json

@azooz2003-bit

Copy link
Copy Markdown
Collaborator Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

@azooz2003-bit

Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 8, 2026 •

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@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/messages/de.json`:
- Around line 3465-3467: The German pricing copy in billingUnavailable uses
informal address, which deviates from the established tone in de.json. Update
the translation in the pricing.billingUnavailable entry to use formal Sie-form
wording, and keep the phrasing consistent with the existing locale convention
used throughout de.json.
🪄 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: 627f06e9-8971-4a02-a373-d429230968ee

📥 Commits

Reviewing files that changed from the base of the PR and between 5abff07 and 411ff15.

📒 Files selected for processing (24)
  • .asc/workflow.json
  • .github/workflows/ios-app-store.yml
  • Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/SetupHelpGateContent.swift
  • Packages/iOS/CmuxMobileShellUI/Tests/CmuxMobileShellUITests/SetupHelpGateContentTests.swift
  • ios/scripts/upload-testflight.sh
  • ios/scripts/validate-app-store-release.sh
  • web/messages/ar.json
  • web/messages/bs.json
  • web/messages/da.json
  • web/messages/de.json
  • web/messages/es.json
  • web/messages/fr.json
  • web/messages/it.json
  • web/messages/km.json
  • web/messages/ko.json
  • web/messages/no.json
  • web/messages/pl.json
  • web/messages/pt-BR.json
  • web/messages/ru.json
  • web/messages/th.json
  • web/messages/tr.json
  • web/messages/uk.json
  • web/messages/zh-CN.json
  • web/messages/zh-TW.json

Comment thread web/messages/de.json
Comment on lines +3465 to +3467
"pricing": {
"billingUnavailable": "Die Abrechnung ist derzeit nicht verfügbar. Bitte versuche es später erneut."
},

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

Use formal address in de.json per the locale's established convention.

The string "Bitte versuche es später erneut" uses the informal du imperative ("versuche"). Per the repo's locale tone convention, de.json should use formal address (Sie form). The formal imperative would be "Bitte versuchen Sie es später erneut."

Based on learnings, de.json should use formal address; only flag when a given locale file deviates from its own established tone/addressing convention.

🛡️ Proposed fix for formal address
-    "billingUnavailable": "Die Abrechnung ist derzeit nicht verfügbar. Bitte versuche es später erneut."
+    "billingUnavailable": "Die Abrechnung ist derzeit nicht verfügbar. Bitte versuchen Sie es später erneut."
📝 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
"pricing": {
"billingUnavailable": "Die Abrechnung ist derzeit nicht verfügbar. Bitte versuche es später erneut."
},
"pricing": {
"billingUnavailable": "Die Abrechnung ist derzeit nicht verfügbar. Bitte versuchen Sie es später erneut."
},
🤖 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/messages/de.json` around lines 3465 - 3467, The German pricing copy in
billingUnavailable uses informal address, which deviates from the established
tone in de.json. Update the translation in the pricing.billingUnavailable entry
to use formal Sie-form wording, and keep the phrasing consistent with the
existing locale convention used throughout de.json.

Source: Learnings

@azooz2003-bit
azooz2003-bit enabled auto-merge (squash) July 8, 2026 23:52
@azooz2003-bit
azooz2003-bit merged commit 2e50b81 into main Jul 9, 2026
36 of 38 checks passed

@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 411ff15. Configure here.

plutil -p "$ent" >&2 || true
rm -rf "$workdir"
return 1
fi

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Beta IPA Apple Sign-In gate

Medium Severity

verify_ipa_aps_environment_production now requires com.apple.developer.applesignin to be Default for every lane, but the TestFlight workflow only validates aps-environment on the beta provisioning profile. If the beta profile does not authorize Sign in with Apple, re-sign can drop that entitlement and uploads that previously passed will start failing at the new check.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 411ff15. Configure here.

enterprise: (
enterprise: appStorePaymentGated ? (
<DisabledButton size="compact">{pricing.billingUnavailable}</DisabledButton>
) : (

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

App Store pending banner link

Low Severity

App Store distribution mode disables checkout and portal links on the pricing cards, but appPricingBanner still returns a welcome=pending action pointing at /api/billing/confirm without checking isAppStoreDistributionMode. A pending-checkout banner in an App Store webview can still expose a billing-management URL.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit 411ff15. Configure here.

This branch was successfully deployed

1 active deployment
Preview – cmux — 411ff154 Deployed Jul 9, 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