ADR 0116 — iOS App Stack: Native Swift/SwiftUI
Status: Accepted Date: 2026-04-22 Deciders: software-architect Scope: Raxx iOS companion app (all of it)
Context
The Raxx iOS companion app must support passkey authentication via AuthenticationServices, real-time trading data via the existing Raptor API, and push notifications via APNs. We must choose a delivery mechanism: native Swift/SwiftUI, React Native (with a native passkey bridge), Capacitor/Ionic (Cordova-style WebView wrapper), or a Progressive Web App (PWA) added to the home screen from Safari.
The key constraint is that WebAuthn / passkeys on iOS are only accessible through the native AuthenticationServices framework (ASAuthorizationController). This is not available via WKWebView, and App Store review has historically rejected apps that are thin wrappers around a web URL without meaningful native functionality.
Decision
Build a native Swift/SwiftUI application. Use AuthenticationServices directly for passkey auth. Consume api.raxx.app via URLSession / async-await. Use UserNotifications + APNs for push.
Consequences
Positive
AuthenticationServicesis used without a bridge. Passkey credential assertions are first-class API calls — no JavaScript shim, no WKWebView message-passing, no risk of the bridge silently failing.- SwiftUI + Xcode Instruments provides the performance profiling and accessibility tooling expected for financial apps.
- App size is minimal (no bundled JS runtime).
- App Store review is straightforward: no guideline 4.7 (HTML5 wrapper) risk.
- The codebase is auditable by iOS engineers without any JavaScript-to-native translation layer.
Negative / risks
- iOS-only. An Android companion app is a separate project and cannot share UI code from this effort.
- Requires Swift/SwiftUI expertise on the team. If only web engineers are available, there is a ramp-up cost.
- The web frontend (
Antlers) and the iOS app cannot share component code. API contracts and business logic live in Raptor and are already shared implicitly.
Neutral
- The existing Raptor API endpoints are consumed as-is. No iOS-specific BFF is introduced in v1.
Alternatives considered
React Native
Rejected. React Native can call native modules via a bridge, and a react-native-passkey community library exists. However: (a) it adds a JS runtime and bridge maintenance surface; (b) Antlers is not a React Native app, so there is no code-sharing benefit; (c) the bridge between React Native and AuthenticationServices is a community package, not Apple's sanctioned path, introducing a dependency we cannot control. For a financial app that gates live trading behind auth, bridge reliability is not acceptable.
Capacitor / Ionic
Rejected. Capacitor's passkey plugin exists but is community-maintained and routes through WKWebView. The App Store has rejected thin WebView wrappers under guideline 4.7. The UX for passkey sheets from within a WKWebView is inferior to the native sheet on iOS 16+.
PWA (Safari home-screen app)
Rejected. Safari on iOS supports WebAuthn from the browser, but a saved-to-home-screen PWA cannot request push notification permissions (as of iOS 16.4, limited PWA push is available, but APNs integration for PWA is still restricted and unreliable for financial alerts). PWAs also cannot access background app refresh or rich notification actions. Not adequate for v1 requirements.
Compliance checklist
- [x] Passkey auth path uses only Apple-sanctioned
AuthenticationServicesAPI — no credentials stored in the app. - [x] Session token stored in Keychain with
kSecAttrAccessibleWhenUnlockedThisDeviceOnly— not synced, revocable server-side. - [x] No U/P path in the app.
- [x] GDPR: APNs device token erasable on DSR (see ios-app.md §Push notifications).
Revisit when
- Raxx adds an Android companion app (separate ADR for cross-platform vs. separate native).
- Apple significantly changes the
AuthenticationServicesAPI or passkey sync behavior.