This is a native iOS shell for the existing xMatrix web app. Electron cannot run on iOS, so this target reuses the same /app web surface through WKWebView and injects a compatible window.xmatrixDesktop bridge.
open apps/ios/xMatrix.xcodeprojSelect the xMatrix scheme, choose an iPhone simulator or device, then run.
The app loads the Web named by its XMatrixWebURL, which is the XMATRIX_WEB_URL build setting (https://xmatrix.sh/app by default). It trusts only that Web's exact https host with in-place navigation and the bridge; the stable Web is also trusted under www.xmatrix.sh. scripts/ios-testflight.sh sets the build setting from the release version: a beta X.Y.Z-beta.N loads https://beta.xmatrix.sh/app, a stable X.Y.Z loads https://xmatrix.sh/app. The App Store version drops -beta.N, so the archive is the only place a build learns its channel.
For local development, set XMATRIX_WEB_URL to a reachable URL, either under the xMatrix target's User-Defined build settings or on the command line:
xcodebuild -project apps/ios/xMatrix.xcodeproj -scheme xMatrix XMATRIX_WEB_URL=http://localhost:3001/app buildWhen running on a physical device, use the Mac LAN IP instead of localhost.
Production Release Intent with release_components=ios runs the existing immutable
release train, simulator gate, signing, and TestFlight upload. A beta stops at
TestFlight; a stable continues with App Store version metadata and review
submission. Both use the release's X.Y.Z as the store version and the run ID as the build number. The app icon is bundled by Xcode; selecting
the exact uploaded build updates the store icon as part of that version.
Public listing text lives in app-store/metadata.json. Contact information, review
credentials, and evidence remain in App Store Connect; never commit them here.
See the App Store CD runbook for first-time
setup, deferred review states, private notes, and recovery.
The release helper archives, exports, and uploads the app:
IOS_DEVELOPMENT_TEAM=YOUR_APPLE_TEAM_ID \
IOS_BUILD_NUMBER=2026050601 \
ASC_API_KEY_ID=YOUR_APP_STORE_CONNECT_KEY_ID \
ASC_API_ISSUER_ID=YOUR_APP_STORE_CONNECT_ISSUER_ID \
ASC_P8_PATH=/path/to/AuthKey_YOUR_KEY_ID.p8 \
PNPM ios:testflightThe TestFlight marketing version is the root version.json version. IOS_MARKETING_VERSION may only be set to that same value; mismatches fail before archiving.
Set SKIP_TESTFLIGHT_UPLOAD=1 to stop after exporting the IPA.
The injected bridge mirrors the Electron preload API used by apps/web/src/lib/desktop/bridge.ts:
getContext,setBadge,notify, andopenExternalmap to native iOS APIs.pushDeviceasks for notification permission and returns the device's APNs token, which the web app registers with the Hub; the app carries theaps-environmententitlement for it.checkCliInstalledalways returns{ "installed": false }.- Desktop auto-update methods return a disabled status because iOS updates are handled by the App Store or TestFlight.
xmatrix://channel/:idandxmatrix://login?...deep links are mapped to the same web routes as the Electron app.<a download>links (including the blob: URLs attachment downloads use) andContent-Disposition: attachmentresponses become nativeWKDownloads: MP4/MOV videos and images are saved straight to Photos, everything else is offered to Files.
On iPhone the bottom tab dock is rendered by a native UIKit tab bar instead of the web surface. The dock has Pages, Channels, Agents, and More, in the web dock's order.
- The app targets iOS 26 and uses the system
UITabBarwithout a custom appearance. It is hosted by a transparent controller with no content child, so the native dock never covers theWKWebView. iOS supplies the native selection treatment and accessibility adaptations; the app does not simulate those with gradients, materials, overlays, or shaders. - The dock floats on the screen bottom, not on the home-indicator inset. Its side and bottom gap is the display corner radius minus the capsule's own corner radius (never under 12pt, and never so wide that the capsule is under 240pt), so the capsule shares a center with the device corners. The web layer receives that gap and the capsule's height above the home indicator.
- The app is one
WKWebView. A tab tap callsonMobileTabChangelisteners in that page, which switch to the tab's pane; the web shell keeps one mounted pane per dock tab, so switching tabs never loads, boots, or rebuilds a screen. Deep links load in the same page. setMobileTabState({ visible, activeView })reports the page's current view and whether the dock shows; the native bar highlights the tab that owns the view (Pages, Channels and Agents own themselves, every other view is in More). The web layer also sendsspaceIdanduserId, which only older app builds that kept a page per tab use.- The web-rendered
MobileTabDockis hidden when the iOS shell is detected, so only the native capsule dock is visible.
Run xcodebuild -project apps/ios/xMatrix.xcodeproj -scheme xMatrix -destination 'platform=iOS Simulator,name=iPhone 17 Pro' test from the repository root (or select another available iOS 26 simulator).
PR CI and the production release gate share .github/actions/ios-simulator-test:
they select Xcode and an installed iOS 26 iPhone by UDID. The release gate tests
the exact stamped release revision and fails if no suitable simulator is available.
The xMatrixTests target exercises the production coordinator with real WKWebView instances whose top-level load calls are recorded instead of sent to the network. It verifies single-page startup, tab taps that switch panes without loading, deep links in the same page, foreground resume, and bridge state decoding. These are lifecycle tests, not end-to-end network latency benchmarks.