11 KiB
Claude iOS Plan — Part 2 (build to parity) + Part 3 (iOS QA)
Program: Part 1 = Android QA (
ClaudeQAPlan.md, in progress) → Part 2 = build the iOS app to Android's CURRENT state → Part 3 = run the same QA passes on iOS. This doc covers Parts 2 & 3.
Decisions (locked)
- Approach: native SwiftUI + Firebase iOS SDK (the
iphone/scaffold already chose this: SwiftPM + XcodeGen, feature dirs mirroring Android,ARCHITECTURE_AUDIT.md). Do NOT switch to KMP/Compose-MP. - E2EE: full Tink-compatible crypto — iOS must byte-match Android's wire formats so an iOS↔Android couple decrypts each other. (Overrides the audit's "skip E2EE for MVP" note — encryption is the cornerstone.)
- Scope: working parity build (Simulator + real device, TestFlight optional). No App Store submission.
⚠️ Hard constraint — macOS required (we are on Linux)
iOS build/run/QA needs macOS + Xcode + iOS Simulator; there is no adb-equivalent from Linux. Therefore:
- On this Linux box I can only: author/refresh Swift source, refresh the audit, write the plan/specs, and reason about the code. I cannot compile, run the Simulator, or do interactive/visual QA here.
- Everything that requires building or running is deferred to a Mac (a Mac, cloud-Mac, or macOS CI runner).
- Part 2 done-on-Linux = "code authored + self-reviewed"; Part 2 truly done = builds green in Xcode and runs on Simulator + device (on a Mac). Part 3 (iOS QA) is entirely macOS-gated.
Current iOS state (as found)
iphone/ = scaffold only: ~19 stub Swift files, Crypto/ empty, most feature dirs empty, one FirestoreService.
ARCHITECTURE_AUDIT.md is thorough but generated from Android v0.2.0 — it predates Messages/conversations,
chat media (photo/GIF/sticker/voice), reactions/typing/read-receipts/pagination, and couple-shared premium (A-001).
So this is a near-full build, not a top-up.
Part 2 — Build iOS to current Android parity
2.0 Refresh the audit to CURRENT Android (do first)
Update iphone/ARCHITECTURE_AUDIT.md from the live Android code: add the Messages tab (conversations inbox +
conversation chat: text/photo/GIF/sticker/voice, reactions, delete/tombstone, typing, read-receipts, pagination,
chat-head bubble), the conversations Firestore model, the new notification types, and couple-shared premium
(CouplePremiumChecker — either partner premium unlocks all features for both). Re-tally screens/models/functions.
2.1 Project + dependencies (Mac)
XcodeGen/project.yml; SPM: Firebase Auth/Firestore/Functions/Messaging/Storage, RevenueCat (purchases-ios),
GoogleSignIn; GoogleService-Info.plist; entitlements (push, keychain); App Check via DeviceCheck/App Attest
(Android uses Play Integrity); APNs setup for FCM.
2.2 Core infrastructure
AppDependencies (manual DI), AuthService + AuthRateLimiter (mirror Android limits), FirestoreService +
per-domain services (User/Couple/Invite/Question/Answer/Game/Date/Conversation), NotificationService
(APNs+FCM + deep-link routing mirroring PartnerNotificationType), navigation (TabView + NavigationStack per tab,
AppRoute mirror, deep-link handler), CloserTheme (light+dark), reusable components.
2.3 E2EE — full Tink-compatible crypto (HIGHEST RISK; cornerstone)
Implement byte-compatible Swift crypto for every Android wire format:
SealedAnswerEncryptor(AES-256-GCM, 96-bit IV, AAD"{coupleId}|{questionId}|{userId}",sealed:v1:),FieldEncryptor(enc:v1:, AAD=coupleId) — used by messages/previews/dates and now profile metadata (displayName+sexinusers/{uid}),AnswerCommitment(SHA-256,sha256:),UserKeyManager(ECIES P-256 HKDF-HMAC-SHA256 AES128-CTR-HMAC, keypair in Keychain,pub:v1:),ReleaseKeyEncryptor(keybox:v1:),RecoveryKeyManager(Argon2id m=46MiB, t=3, p=1, BIP39-style wordlist),CoupleEncryptionManager, Keychain-backed key stores.- Interop test harness (the acceptance gate): (a) decrypt Android-produced fixtures (
sealed:v1:/enc:v1:/keybox:v1:) on iOS; (b) have Android decrypt iOS-produced ciphertext; (c) verify an Android-generated recovery phrase unlocks on iOS and vice versa. Wire formats must match byte-for-byte. If a format can't be matched with CryptoKit, use a vetted Swift lib (e.g. swift-sodium/SwiftArgon2) — never a non-interoperable shortcut. - Golden vectors for the deterministic primitives (checked into both repos):
AnswerCommitmentSHA-256 andRecoveryKeyManagerArgon2id (fixed salt/params) must produce identical bytes on both platforms — assert in unit tests on each side. AEAD/ECIES use random IVs so they can't be golden-matched; cover those with the round-trip harness above. Generate the Android fixtures now (on Linux) so iOS has them ready. - ⛔ Profile-metadata decrypt-on-read (REQUIRED before iOS launch — R22). Android now encrypts
displayName+sexunder the couple key. iOS reads these raw today (HomeViews, SettingsViews) and will showenc:v1:…until it decrypts them on read (tolerant of legacy plaintext; show a 🔒 placeholder when the key is missing) — mirror Android'sFirestoreUserDataSourcechokepoint + the pairing/legacy migration and unpair-revert. Until this ships, iOS shows the locked placeholder for name/gender (acceptable in dev; not for release). - ⛔ Date reflections (REQUIRED before iOS launch — R22). The new Date Memories & Replay feature adds an E2EE
couples/{id}/date_reflections/{dateId}/answers/{uid}collection with the same couple-key + read-gatedsecure/payloadreveal as the daily question (date_historyis plaintext). iOS must implement the matching reflection write/decrypt/reveal (mirrorFirestoreDateReflectionDataSource) and thedate_reflection_*/date_loggednotification types; until then iOS can't participate in date reflections. - ⛔ Conversation backup + full partner-assisted restore (REQUIRED before iOS launch — R24). Android now keeps a
couple-key-encrypted conversation backup (
couples/{id}/backup/manifest+.../chunks/{seq}enc:v1:; snapshot blob at Storageusers/{uid}/backups/{id}) and a partner-assist flow (couples/{id}/restore_requests/{uid}with a freshpub:v1:+ partner-writtenkeybox:v1:; ECIES context"{coupleId}|restore|{sender}|{recipient}|{nonce}"; 6-digit OOB code =truncate6(SHA-256(pubkey‖nonce))). For cross-platform restore, iOS must byte-match theBackupCodecJSON envelope, the couple-key wrap, and the keybox context/code so an iOS device can restore (self or partner-assisted) from an Android partner and vice-versa — same class as the existing E2EE interop gate. Also wire therestore_requestednotification type. Until then iOS can't back up/restore or help a partner restore. - ⛔ Partner-assist consent hardening (R24-b). iOS's consent screen must mirror Android: resolve the recipient
via the user service and show their email (plaintext anchor) + locally-decrypted display name, and gate
Approve on both the 6-digit code AND an explicit "I reached them" confirmation. Mirror the two lifecycle
fixes — delete any existing
restore_requests/{uid}before re-creating (aset()over an existing doc is a rule-denied key-changing update) and reject an expired request at fulfil. Handle the newrestore_self_alertnotification type (route to account security). The server (onRestoreRequested/onRestoreFulfilled) is shared, so this is purely the iOS client half.
2.4 Screens & features to parity (~48 + new messaging)
All routes from the refreshed audit's screen map, including the NEW Messages experience (inbox + conversation
with full media/reactions/typing/read-receipts/pagination) and the games (full, not stubs), dates, wheel, settings,
paywall. Mirror the Android UX + the couple-shared premium gating (route gated features through the iOS
EntitlementChecker couple-shared equivalent).
2.5 Build & smoke (Mac)
Xcode build green; run on Simulator + a real device; smoke each major flow (auth → pair → home/today/play/messages/ settings) with no crashes. This step + 2.1/2.5 runs on a Mac.
Part 3 — iOS QA (reuse ClaudeQAPlan.md passes A–E on iOS)
Same methodology, severity scale (P0–P3), report-only→fix→re-QA-until-flawless loop, continuity (use
ClaudeReport-iOS.md + ClaudeQACoverage-iOS.md + run-state header), and autonomous run-to-completion mode.
macOS-gated. Tooling adaptation from Android:
| Android (Part 1) | iOS (Part 3) |
|---|---|
adb install/tap/screencap |
xcrun simctl install/io screenshot; XCUITest or idb to drive taps |
| 2 emulators (5554/5556) | 2 iOS Simulators (or Simulator + device) for the two partners |
adb logcat FATAL/ANR |
xcrun simctl spawn booted log stream / Console crash logs |
cmd uimode night for theme |
Simulator Appearance (light/dark) per device |
premium toggle via set_premium.js |
same admin script (server-side) |
Run all passes on iOS:
- A couple-shared premium (gates + paywall), B each game one full playthrough on both, C visual light+dark all screens + navigation-from-every-entry + back-stack/double-back, D security/encryption, E notifications (APNs/FCM delivery + tap-to-open every type).
- NEW — cross-platform pass (X): a mixed couple (Android + iOS) — messaging, answers, dates, premium, and notifications work cross-platform, and E2EE decrypts both ways (the Part 2 interop gate, verified live on real paired devices). This is the make-or-break for the cornerstone.
- iOS-native dimensions (add to the passes): Dynamic Type (largest sizes), VoiceOver, safe-area / notch /
Dynamic Island, multiple device sizes (SE → Pro Max; iPad if supported), edge-swipe-back gesture + interactive
pop (the iOS "double-back"/nav-stack analog),
scenePhasebackground/foreground, dark/light. - Real-device / sandbox needs: App Attest/DeviceCheck and APNs push don't fully work on the Simulator —
use a real device (or
xcrun simctl pushwith a payload for local notif routing); RevenueCat IAP needs a StoreKit config file or a sandbox Apple ID. Plan Pass A/E around this.
Definition of done
- Part 2: iOS builds green + runs on Simulator + device at feature parity with current Android; E2EE interop harness passes (Android↔iOS decrypt both ways).
- Part 3: an iOS QA round is flawless (zero P0–P2, D+E clean, every game fully played, nav/back verified) AND the cross-platform pass X is clean.
What can proceed now (Linux) vs needs a Mac
- Now (Linux): 2.0 audit refresh; author Swift for models/services/crypto/screens; write iOS QA tooling scripts; prepare Android-produced crypto fixtures for the interop harness.
- Needs a Mac: 2.1 project/deps, all compiling, 2.5 build+run, and all of Part 3. Surface this as the blocker when execution reaches it (per the autonomous-mode rule: do all non-gated work first, then flag the Mac requirement).