feat(ios): state connection method and live transport in diagnostics reports
cmux iOS
SwiftUI iOS/iPadOS shell for the CMUXMobileCore production path.
Current phase:
- Stack Auth sign-in gate with Apple, Google, email code, and a debug-only
42shortcut - QR/manual pairing surface
- CMUXMobileCore pairing payload and attach-ticket decoding
- injectable
CmxByteTransportFactoryruntime hook - isolated preview host data when no concrete transport is installed
- workspace list, workspace detail, terminal dropdown, and input bar
No Rust, Iroh, or Zig dependency is linked into this shell. Concrete route implementations should enter through CMUXMobileRuntime.
Build and reload the simulator:
ios/scripts/reload.sh --tag iossh
Run package tests:
swift test --package-path ios/cmuxPackage
Build compatibility and production-auth DEV builds
A DEV iOS build connects only to the Mac DEV build with the same tag. BETA, INTERNAL, and App Store iOS builds connect only to Stable or Nightly Mac builds. Account environment does not change that compatibility boundary.
Use --prod-auth only when a tagged DEV build needs to test production account,
registry, or API behavior:
ios/scripts/reload.sh --tag my-tag --device-only --prod-auth
What --prod-auth does:
- Bakes
CMUXAuthEnvironment=productioninto the app's Info.plist (via theCMUX_IOS_AUTH_ENVbuild setting), so the build signs in against the production Stack project and useshttps://cmux.comfor the device registry/API and the magic-link callback. - Makes the presence worker follow the auth channel: the app resolves the
production presence instance (see
PresenceClient.productionServiceURL) so compatible Macs appear in Computers. The worker URLs live only in Swift; the script bakes no copy, and an explicitCMUX_PRESENCE_BASE_URLstill wins. - Skips the dogfood auto sign-in/auto-pair (those credentials belong to the development Stack project). Sign in in-app with the same account as your matching tagged DEV Mac.
- On first launch after switching auth environments on the same install, the app clears the previous environment's session/caches (tokens and user ids are per-Stack-project), so you start signed out instead of restoring a stale identity.
Every installed iOS bundle registers one URL scheme derived from its complete
bundle identifier, for example cmux-ios-dev.cmux.app.beta://… or
cmux-ios-dev.cmux.ios.my-tag://…. No two cmux builds register the same system
route. The authenticated Mac status supplies the exact instance tag, which the
app validates before it saves or adopts the connection.
Without the flag, the same override is available by bundling a
LocalConfig.plist with an AuthEnvironment string of production (see
MobileAuthComposition); a LocalConfig.plist entry wins over the baked
Info.plist value.
TestFlight beta (cloud lane)
ios/scripts/cloud-testflight.sh is the turnkey lane for cutting a TestFlight
beta. It builds the heavy GhosttyKit + Swift Release compile on a leased fleet
Mac (same maclease pool as the device cloud reload, m1ultra excluded), so the
build stays off this Mac's CPU. The fleet produces an UNSIGNED Release archive
for the beta bundle id dev.cmux.app.beta (no signing material ever lands on
the shared Macs), downloads it locally, then hands it to
ios/scripts/upload-testflight.sh --archive-path, which does the local export,
re-sign with the Apple Distribution cert (re-adding aps-environment=production),
strict codesign verification, and TestFlight upload.
# Dry run: build + export + re-sign + verify aps-environment=production, NO upload
ios/scripts/cloud-testflight.sh --no-upload
# Full lane: build on the fleet and upload to TestFlight (internal "cmux beta" group)
ios/scripts/cloud-testflight.sh
# Also make the build eligible for external testers
ios/scripts/cloud-testflight.sh --external
A standalone cmux clone with no cmuxterm-hq checkout transparently falls back to
a LOCAL Release archive (--local forces it), then takes the same export path.
Internal testers (the cmux beta group) get every uploaded build instantly with
no review. An --external build is different: the FIRST external build of a new
MARKETING_VERSION must pass a one-time Apple Beta App Review (~24h) before any
external tester can install it. Subsequent external builds of the same version
ship without re-review. External cuts reuse
CMUX_IOS_BETA_MARKETING_VERSION from ios/Config/Shared.xcconfig; bump it
only when you want a fresh Beta App Review cycle. The upload path assigns the
processed build to both the Founder's Edition and Pro external beta groups. It
uses CMUX_TESTFLIGHT_EXTERNAL_GROUP_ID /
CMUX_TESTFLIGHT_EXTERNAL_GROUP_NAME for the primary group and
CMUX_TESTFLIGHT_PRO_GROUP_ID for Pro. GitHub Actions maps the repository
variables IOS_TESTFLIGHT_EXTERNAL_GROUP_ID,
IOS_TESTFLIGHT_EXTERNAL_GROUP_NAME, and IOS_TESTFLIGHT_PRO_GROUP_ID to those
script inputs. When Apple reports the build as READY_FOR_BETA_SUBMISSION, the
same lane also creates the beta app review submission automatically so a new
MARKETING_VERSION is not left stuck at "Ready to Submit".
If CI is moved back from a pending higher version to the last approved version, external testers are unblocked because they could not install the pending build. Internal testers who already installed that higher internal-only build will not see lower-version builds as updates in TestFlight. They need a one-time app reinstall, or operators need to cut an internal-only build on the higher version.
TestFlight GitHub Actions signing
.github/workflows/ios-testflight.yml uses manual export signing because Xcode's
automatic App Store Connect export has produced IPAs whose signed app
entitlements omit aps-environment=production. That upload is intentionally
blocked because TestFlight push would silently fail. Scheduled main uploads
ship only to the internal TestFlight group. A manual run with
marketing_version_override switches to the external beta bundle, installs its
provisioning profile, and assigns the processed build to both the Founder's
Edition and Pro groups. That external override reuses an already approved
marketing version and auto-submits the first build when Apple reports that Beta
App Review is required.
Required GitHub secrets:
ASC_API_KEY_IDASC_API_ISSUER_IDASC_API_KEY_P8_BASE64IOS_DISTRIBUTION_CERTIFICATE_BASE64(base64-encoded.p12for an Apple Distribution certificate on team7WLXT3NR37)IOS_DISTRIBUTION_CERTIFICATE_PASSWORDIOS_BETA_PROVISIONING_PROFILE_BASE64(base64-encoded App Store profile fordev.cmux.app.beta, withaps-environment=production)
App Store production lane
The production App Store lane is separate from the TestFlight beta lane. It uses the same archive/export/re-sign verification path, but switches the submitted identity to the App Store bundle id and stops before App Review submission unless the operator explicitly confirms submission in CI.
# Build, export, re-sign, verify, and upload the production App Store build
ios/scripts/upload-app-store.sh
# Dry run: export + re-sign + verify aps-environment=production, no upload
ios/scripts/upload-app-store.sh --export-only
# Run the read-only ASC readiness package after upload
ios/scripts/validate-app-store-release.sh --app "$ASC_APP_ID" --build-number "$CF_BUNDLE_VERSION" --wait-build --strict
Defaults:
- Bundle ID:
com.cmux.app - Marketing version:
CMUX_IOS_APPSTORE_MARKETING_VERSIONinios/Config/Shared.xcconfig - Display name:
cmux - Provisioning profile:
cmux App Store Distribution - Entitlements:
Config/cmux-release.entitlements
The review package lives in ios/AppStoreReview/:
review-notes.mdis the pasteable App Store Connect Review Information notes source.metadata-screenshots-checklist.mdis the blocking checklist for metadata, screenshots, privacy, account deletion, and payment gating.
.github/workflows/ios-app-store.yml is manual-only. It uploads a production
build, waits for ASC processing, runs ios/scripts/validate-app-store-release.sh,
and submits for review only when submit_for_review is set.
Additional production workflow requirements:
- Repository variable
IOS_APPSTORE_APP_ID - Secret
IOS_APPSTORE_PROVISIONING_PROFILE_BASE64(base64-encoded App Store profile forcom.cmux.app, withaps-environment=production)