Skip to main content

iOS Parity Migration

apps/ios was built against an earlier version of the backend and has drifted. The shared operations package now declares 746 GraphQL operations; apps/frontend-nextjs uses 649 of them, apps/ios resolves 75. On top of that, seven operation types the iOS code still references no longer exist in the package at all, so the app cannot be regenerated cleanly today.

This page is the working plan to bring iOS to parity with web. It is a living checklist: tick the boxes as work lands — nothing regenerates this page.

Scope decision on record: full parity. Every operation and feature that exists in apps/frontend-nextjs is planned for iOS, except the items listed under Out of scope which are structurally web-only (SEO surfaces, marketing pages, browser-redirect payment returns). Purchase flows are in scope but must be redesigned for StoreKit, not ported.

Related: iOS App · GraphQL · Testing · Roadmap

Where iOS stands today

Operation counts per domain in packages/graphql/operations/Web. "web" = documents imported by apps/frontend-nextjs; "iOS" = generated types referenced by Swift code that still resolve.

DomainOperationswebiOSiOS coverage
AppVersion1000%
Auth5145611%
Calls2752385%
Coins414049%
Contacts6600%
Conversations44152454%
DeviceTokens202100%
Family7700%
Follow393925%
Live343400%
Messages896566%
Misc262500%
Notes4400%
Notifications282400%
Payments66466%
Posts12612400%
Profile2824310%
Reports3300%
Search424112%
Settings484800%
Shop161600%
SocialLinks6600%
Stories474700%
Subscriptions252500%
Total7466497510%

Two things this table hides, both worth knowing before planning:

  • iOS is ahead of web on real-time calling. Calls (23/27) and Conversations (24/44) are the only domains where iOS leads. 45 of the 75 operations iOS uses are not used by web at all — CallKit/VoIP, speaker requests, archived/locked conversations, device tokens. None of that gets rewritten; it gets kept and tested.
  • Some iOS operations are legacy variants that still compile. MeQuery, GetUserQuery, UpdateProfileMutation, LoginWithAppleMutation, LoginWithPhoneMutation, RequestPhoneOtpMutation and SearchConversationsQuery all still exist in the package, but web has moved to newer *Inline documents (GetMe, GetPublicProfile, UpdateProfileInline, LoginWithAppleInline, LoginWithPhoneInline) with richer selection sets. iOS renders less data than web from the same backend.

Phase 0 — Unblock the build and the codegen

Nothing else can be verified until iOS compiles against freshly generated code. This phase is a prerequisite for every phase below.

The generated module was renamed ClosegramAPIClosegramGraphQL (commit 2762f0fa), and packages/apollo-swift/Package.swift now vends the product ClosegramGraphQL. 104 Swift files import ClosegramGraphQL, but apps/ios/app/app.xcodeproj/project.pbxproj still links a product named ClosegramAPI, which no longer exists — the string ClosegramGraphQL appears zero times in the project file.

  • Replace the ClosegramAPI package product dependency with ClosegramGraphQL in the app target and commit project.pbxproj — done; project.pbxproj links ClosegramGraphQL (0 remaining references to the old product name)
  • Confirm xcodebuild -scheme app build succeeds from a clean derived-data state — reconfirmed after the Phase 1 NavigationStack rewrite (see below)
  • Update the stale ClosegramAPI references in prose: apps/ios/README.md (lines 136, 681), apps/ios/app/app/Core/Services/Auth/README.md (line 194), packages/apollo-swift/README.md — still stale, not touched yet

Fix the seven dead operation references

These generated types are referenced by Swift code but cannot be produced by codegen, because no .graphql document declares them. Resolutions differ per case — three need a new shared document, three need to adopt the renamed one, one needs a redesign.

iOS referencesBackend statusResolution
ChangePasswordMutationField changePassword existsAdopt ChangePasswordInline (Auth/ChangePasswordInline.graphql) → ChangePasswordInlineMutation
ResetPasswordMutationField resetPassword existsAdopt ResetPasswordInline (Auth/ResetPasswordInline.graphql) → ResetPasswordInlineMutation
LoginWithFirebaseMutationField removed from the schemaMigrate to loginWithIdToken (Auth/LoginWithIdToken.graphql) — this is the Google/Firebase path web uses
DeleteAccountMutationField deleteAccount(password: String!) exists, no shared documentAdd a document under Auth/ or adopt the newer flow web uses: RequestAccountDeletionInline + CancelAccountDeletionInline + DeleteAccountImmediatelyInline (Settings)
MyPurchaseHistoryQueryField myPurchaseHistory existsResolved — rather than switching to MyCoinPurchases/MyRecentTransactions, a MyPurchaseHistory($limit, $offset) document was added to Coins/MyCoinPurchases.graphql and codegen regenerated; iOS resolves it directly
SearchMyMessagesQueryField searchMyMessages exists, no shared documentAdopt SearchMessagesInline (Messages/SearchMessagesInline.graphql), which is what web uses
RefreshTokenMutationNo such mutationrefreshToken is only a String field on the auth responseRedesign session refresh: persist the refreshToken returned at login and re-authenticate, or re-mint a Firebase ID token and call loginWithIdToken
  • Decide, per row, whether to add a shared document or adopt the existing one
  • Add any missing documents to packages/graphql/operations/Web/** (per the root CLAUDE.md, mobile-only operations still live under Web/, shared with apollo-web and apollo-swift)
  • Rewrite the seven call sites in Swift
  • Run npm run codegen:ios and commit the regenerated packages/apollo-swift/Sources/ClosegramGraphQL/Sources

Make drift impossible to reintroduce

  • Add an iOS CI job (macOS runner) that runs codegen and fails if the generated sources are dirty — landed as a dedicated .github/workflows/ios-ci.yml (not folded into ci.yml, which only covers backend/frontend), moved there after being found to live at the typo'd path .githubtest/workflows/ios-ci.yml and never actually run on GitHub Actions. Also fixed stale XCODE_VERSION: '16.1' / macos-15 runners, neither of which support this project's IPHONEOS_DEPLOYMENT_TARGET = 26.1
  • Same job builds the app scheme and runs appTests (see Testing)
  • Document the refresh ritual in iOS App: pull → npm run codegen:schemanpm run codegen:ios → build

Adopt the current document variants

Legacy documents that still compile but no longer match what web renders. Low risk, do it while the codegen is fresh.

  • MeQueryGetMe / GetMeProfileInline
  • GetUserQueryGetPublicProfile
  • UpdateProfileMutationUpdateProfileInline
  • LoginWithAppleMutationLoginWithAppleInline
  • LoginWithPhoneMutationLoginWithPhoneInline
  • RequestPhoneOtpMutation — confirm the current variant, then align
  • Diff each new selection set against the iOS model mapping and widen the domain models where fields were added

Phase 1 — Architecture adjustments

These are structural fixes that make the feature phases cheaper. Do them before the volume work, not after — every one of them gets more expensive per feature added.

Layering violations

  • Remove direct Apollo calls from SwiftUI views: ChatsListView.swift (lines 870, 959), ParticipantActionsSheet.swift (lines 207, 226), ContactInfoView.swift — route them through the Chat repository
  • Add Features/Calls/Data/Repositories/CallRepository and stop CallStore (+ its five extensions) from calling GraphQLService/ChatAPIService directly — 22 direct service calls today
  • Establish the rule in review: View → ViewModel → Store → Repository → Service, no shortcuts

Dependency injection

  • Register the four unregistered repositories in DIContainer+Registration.swift: SearchRepository, PaymentRepository, CoinPackageRepository (currently instantiated via default init arguments) — plus any repository added by the phases below
  • Add all of them to validateRequiredDependencies(), which today validates 3 of 7 and therefore gives false confidence
  • Fixed a related but distinct DI gap: SearchView, CreateDirectChatView and CreateGroupChatView (plus, before it was deleted above, NestedNavigationView's showCreateChat()) each did let searchStore = SearchStore() behind a "Temporary: Create local stores until we can use @EnvironmentObject in init" comment, bypassing the DI-registered singleton and creating fresh throwaway instances instead. All four now resolve DIContainer.shared.resolve(SearchStore.self). This is a Store-level fix, not the Repository-level gap the two bullets above still track

Test seams

  • Move MockAuthRepository and MockChatRepository out of the app target (or wrap in #if DEBUG) — they currently ship in the production binary
  • Delete the duplicate: MockAuthRepository exists twice, in Features/Auth/Data/Repositories/ (2.5 KB) and appTests/Tests/AuthTests/Data/ (7.4 KB), with divergent behaviour — still open, both still exist
  • Add <Testables> for appTests to the shared app.xcscheme — done. TestAction had shouldAutocreateTestPlan = "YES" but no <Testables> block, so xcodebuild test/fastlane test failed outright with "Scheme app is not currently configured for the test action" rather than silently running zero tests. Fixing it surfaced 9 previously-invisible failures in AuthTests (real gaps in LoginUseCase/RegisterUseCase validation, plus 3 tests asserting the wrong password-strength tier), all fixed for real

Structure

  • Replace the NestedNavigationView/CustomTabBarController UIKit bridge (a UIViewControllerRepresentable wrapping UINavigationController/UITabBarController, manually reconstructing a UIHostingController with a hand-picked .environmentObject() list per pushed route) with a pure-SwiftUI MainTabView: an outer NavigationStack(path: $router.path) around a TabView, each of the 5 tabs with its own inner NavigationStack, .navigationDestination(for: AppRoute.self) for the 4 cross-tab routes. Both old files deleted. This was the root cause of the environment-object-attachment fragility this section used to warn about — a pure SwiftUI tree propagates environment automatically, so no manual re-attachment list to keep in sync exists anymore
  • Consolidate the (now five, Helpers/CustomSheetModifier.swift deleted this pass as fully unused — zero references, and its own design attached zero environment objects) sheet implementations into one place (Helpers/SheetPreferenceKey.swift, Utilities/SheetPresentationState.swift, Stores/SheetCoordinator.swift, Shared/Components/BottomSheet.swift, Chat/.../CustomBottomSheet.swift)
  • Remove the top-level Helpers/, Utilities/ and Stores/ folders — none are part of the declared architecture
  • Decide the fate of Domain/UseCases: it exists only in Auth. Either adopt it in every feature or drop it from the architecture description
  • Split the god objects before adding to them: ChatStore.swift (1411 lines), ChatsListView.swift (1010), LiveKitManager.swift (965), CallStore.swift (873 + 5 extensions). CallStore+* is the pattern to copy
  • Delete or rewrite apps/ios/app/Package.swift — it declares a target ClosegrammApp whose Sources/ directory does not exist and lists only two of the six real dependencies
  • Rotate the Giphy key committed as a DEBUG fallback in Config/Secrets.swift and move both fallbacks to xcconfig/.env
  • Correct apps/ios/README.md, which documents a CacheCoordinator, ApolloClientManager, GoogleAuthManager, Core/Models/User.swift, Views/Messages/ and a TESTING_GUIDE.md that do not exist

New feature scaffolding

Nine new feature modules are needed. Create each as Features/<Name>/{Data/Repositories,Domain/Models,Presentation/{Store,ViewModels,Views}} with a registered repository, per iOS App.

  • Posts · [ ] Stories · [ ] Live · [ ] Notifications · [ ] Settings (promote from views-only) · [ ] Shop · [ ] Subscriptions · [ ] Social (Follow + Contacts + Family) · [ ] Monetization (Coins + Payments + Payouts)

Phase 2 — Auth, account and security

45 Auth operations missing. This is the phase that unblocks account switching, 2FA and passkeys — all of which the backend already supports and web already ships.

  • Password flows on the current documents: ChangePasswordInline, ResetPasswordInline, ChangeForcedPasswordInline, RequestPasswordReset, VerifyResetTokenInline
  • Google sign-in via LoginWithIdToken (replaces the removed loginWithFirebase)
  • Apple sign-in via LoginWithAppleInline; phone OTP via LoginWithPhoneInline + RequestPhoneOtp
  • Passkeys / WebAuthn with ASAuthorizationPlatformPublicKeyCredentialProvider: GeneratePasskeyRegOptionsInline, VerifyPasskeyRegInline, GeneratePasskeyAuthOptionsInline, VerifyPasskeyAuthInline, MyPasskeysInline, DeletePasskeyInline — Face ID/Touch ID makes this a better experience on iOS than on web
  • Two-factor: SetupTwoFactorInline, VerifyTwoFactorSetupInline, EnableTwoFactorInline, DisableTwoFactorInline, TwoFactorStatusInline, RegenerateBackupCodesInline, VerifyLoginTwoFactorInline
  • Multi-account switching: AddAccountLogin, AddAccountGoogle, AddAccountApple, AddAccountPhone, AddAccountVerify2FA, AddAccountVerifyPasskey — needs multi-token Keychain storage, which today assumes a single session
  • Onboarding: CompleteOnboarding, OnboardingInterestSuggestions, OnboardingSearchUsers, OnboardingFollow, OnboardingUnfollow, OnboardingFollowRequirement
  • Sessions and security surface: ActiveSessionsInline, RevokeSessionInline, RevokeAllOtherSessionsInline, LoginHistoryInline, SecurityEventLogInline, SecurityScoreTwoFactorInline, SecurityScoreEmailInline, GetSecurityAlertsInline, MarkSecurityAlertReadInline, DismissSecurityAlertInline
  • Session refresh redesign (from Phase 0) landed and covered by tests
  • Logout wired to clear Keychain, Apollo cache and device tokens
  • i18n strings in en.lproj and es.lproj; tests per Testing

Phase 3 — Profile, social graph and discovery

Profile 25 missing, Follow 37, Search 41, SocialLinks 6, Contacts 6.

  • Public profile on GetPublicProfile + GetRelationshipStatusForProfile + RecordProfileVisitFromProfile
  • Own profile and editing: GetMeProfileInline, UpdateProfileInline, ValidateUsernameInline, GetAboutAccount, GetMyInterests, UpdateInterests
  • Presence: UpdateLastSeen, SetUserOffline
  • Verification and email: RequestVerificationInline, VerificationStatusInline, SubmitIdentityVerificationInline, MyIdentityVerificationStatusInline, GetEmailStatusInline, ResendEmailVerificationInline, VerifyEmailInline
  • Follow graph: followers/following modals, RemoveFollowerModal, close friends, favorites, blocked, muted, restricted (AddToCloseFriendsInline, GetFavoriteUsersInline, GetBlockedUsersInline, GetMutedUsersInline, RestrictedAccountsInline, and the *FromProfile block/mute/restrict variants)
  • Bulk follower actions: BulkUnfollowUsersAction, BulkBlockUsersAction, BulkRemoveFollowersAction, BulkGetUserFollowers, BulkGetUserFollowing
  • Search and discovery: SearchUsersExplore, SearchHashtagsInline, DiscoverPosts, DiscoverTrending, DiscoverRecommended, DiscoverSimilar, DiscoverNearby, DiscoverMayKnow, GetSuggestedUsers, MentionSuggestionsInline, recent-search history
  • Social links: MySocialLinks, AddSocialLink, UpdateSocialLink, DeleteSocialLink, ReorderSocialLinks, UserSocialLinks
  • Contacts import with the native Contacts framework + permission prompt (all 6 Contacts operations)
  • Native share sheet and QR for profiles
  • i18n + tests

Phase 4 — Messaging catch-up

83 Messages operations and 20 Conversations operations missing. iOS already has the transport and the real-time layer; this is feature surface on top of working plumbing.

  • Reactions, stars, pins: AddReaction, GetMessageReactions, GetPinnedMessages
  • Threads and replies: GetMessageThread, GetMessageReplies, GetMessage
  • Delete semantics: DeleteMessageForMe vs DeleteMessageForEveryone (iOS has only the legacy DeleteMessage)
  • In-conversation search: SearchMessagesInline (replaces the dead SearchMyMessagesQuery)
  • Media browser: GetMessagesByMediaType
  • Polls: CreatePoll, GetPollResults
  • Scheduled and expiring messages: CancelScheduledMessage, EditScheduledMessage
  • Message translation, per-message and global (MyTranslationSetting)
  • Group management: GetConversationParticipants, UpdateConversation, UpdateConversationSettings, TransferAdmin, LeaveConversation, DeleteConversation, ConversationInviteLinkInfo, join-by-link
  • Message requests and privacy: ConversationPrivacyInfo, request accept/decline
  • Unread counters: GetConversationUnreadCount, GetTotalUnreadCount, NavTotalUnread — needed for a correct app badge
  • Reporting: ReportConversation
  • Notes rail: MyNote, CreateNote, DeleteNote, NotesFromFollowing (all 4 Notes operations)
  • Paid messages and paid groups — defer the purchase leg to Phase 9, ship the read/entitlement side first (HasConversationAccess, GetMyMessagePurchases)
  • i18n + tests

Phase 5 — Posts and feed

126 operations, the single largest gap and the core of the product. Web uses 124 of them; iOS uses none.

  • Home feed: GetFeed, GetInteractionCounts, view recording
  • Post detail and card: GetPost, HasUserLikedPostInline, IsPostSavedInline, HasUserRepostedInline
  • Create post: media upload, caption, mentions/hashtags, visibility, collaborators, scheduling (CreatePost, GetScheduledPostForEdit)
  • Comments: CreateComment, GetPostComments, comment likes and settings
  • Reposts, saves and collections
  • Likes and viewers modals: GetPostInteractionsForLikesModal, GetMyProfileViewers
  • Post options: edit (GetPostForEdit), archive (ArchiveOwnPost), delete (DeleteOwnPost), pin, report (CreatePostReport)
  • Post insights and promotions: CreatePostPromotionFromComposer, promo stats
  • Hashtag pages: PostsByHashtag, HashtagStatsHashtagPage
  • Archive and recently-deleted with restore
  • Paid post unlock — read side now, purchase leg in Phase 9
  • Dynamic watermark on paid media, matching web's anti-screenshot overlay
  • i18n + tests

Phase 6 — Stories, clips and highlights

47 operations, none on iOS.

  • Story rail, viewer, reactions, replies, view counts
  • Create story: media and text stories with background picker
  • Highlights: GetUserHighlights, GetMyStoriesForHighlight, create/edit/delete/reorder
  • Share story to DM
  • Clips feed and clip creation (like, save, repost, stats)
  • Search history operations that live in this domain (SearchHistoryExplore, ClearSearchHistoryExplore, DeleteSearchHistoryItemExplore)
  • i18n + tests

Phase 7 — Live streaming

34 operations, none on iOS — but iOS already has LiveKit wired for calls, which is most of the hard part.

  • Live discovery: currently live, trending, scheduled, archive
  • Live room: video, comments, reactions, pinned comments, gifts
  • Go live: create/start/update/end, guest tokens, save recording
  • Speaker controls — reuse the existing speaker-request machinery from CallStore
  • Real-time subscriptions: LiveCommentAddedSub, LiveReactionsSub, LiveStreamUpdatedSub, LiveStreamsChangedSub
  • i18n + tests

Phase 8 — Notifications

28 operations, none on iOS. Device-token registration already works (DeviceTokens is at 100%), so this is the in-app surface, not the transport.

  • Notification list and rich list: MyNotifications, MyNotificationsRich
  • Read/delete: MarkNotificationAsRead, MarkAllNotificationsAsRead, DeleteNotification, DeleteAllNotifications
  • Badge and unread count: UnreadNotificationsCount + NavTotalUnread
  • Real-time: NotificationAdded subscription
  • Follow requests: PendingFollowRequests, AcceptFollowRequest, RejectFollowRequest, SendFollowRequestFromProfile, CancelFollowRequestFromProfile, GetSentFollowRequestsForProfile
  • Settings: GetNotificationSettingsInline, UpdateNotificationSettingsInline, extended settings, message previews, GetSleepModeSettingsInline / UpdateSleepModeSettingsInline
  • Map every APNs payload type to an in-app deep link through AppRouter
  • i18n + tests

Phase 9 — Settings, privacy and safety

48 Settings operations plus 26 Misc, none on iOS. Web has roughly 85 settings screens; the iOS Settings feature today is three views with no store.

  • Promote Settings to a full feature module (Data/Domain/Presentation)
  • Privacy: GetPrivacySettingsInline, UpdatePrivacySettingsInline, extended privacy, ResetPrivacySettingsInline, privacy checkup
  • Hidden words, nudity protection, read receipts, tags and mentions, sharing settings
  • Account lifecycle: DeactivateAccountInline, ReactivateAccountInline, RequestAccountDeletionInline, CancelAccountDeletionInline, DeleteAccountImmediatelyInline, AccountStatusInline, GetAccountStandingStatusInline
  • Data export: request, status, download, delete, plus the DataExportStatusChangedInline subscription
  • Time management and usage: GetTimeManagementDataInline, RecordUsageHeartbeatInline, TakeBreakInline, EndBreakInline, GetActivityHeatmapInline
  • Interaction limits: MyInteractionLimitInline, SetInteractionLimitInline
  • Content preferences: RemoveContentPreferenceInline, GetNotInterestedContentInline, UpdateMediaPreferencesInline
  • Location sharing: GetMyLocationSharing, UpdateMyLocationSharing, DeleteMyLocationSharing
  • Translation settings: MyTranslationSetting, GetReelsTranslationSettingsInline
  • Family Center (all 7 Family operations): parent-child link, time limits, usage stats
  • Moderators (Reports domain): MyModerators, AddModerator, RemoveModerator, UpdateModeratorPermissions, ModeratorActivity
  • App version gate: CheckAppVersion at launch — the backend contract was built specifically for this, see App Version Control
  • i18n + tests

Phase 10 — Monetization

Coins 37 missing, Subscriptions 25, Shop 16, Payments 2, plus the tips/top-fans/referrals operations in Misc. This phase is not a port. Web sells coins with Stripe and PayPal; on iOS, digital goods must go through StoreKit or the app gets rejected.

  • Decide the StoreKit boundary and write it down before building: which purchases are IAP (coins, gifts, tips, paid posts, paid messages, creator subscriptions) and which stay card/PayPal (physical shop goods, payouts)
  • StoreKit 2 products and purchase flow, with server-side receipt validation — this needs a new backend contract; the existing CreateCoinPurchaseIntent/CapturePaypalCoinOrder operations do not apply
  • Coin balance and wallet: MyCoinBalance (already on iOS), MyRecentTransactions, MyCoinPurchases
  • Gifts: gift catalog (GiftCatalogInline, LiveGiftCatalogInline), send to user/post/live
  • Tips and top fans: SendTipFromChat, MyReceivedTipsInline, MyTipStatsInline, TopFansInline, TopTippersInline, RewardTopFanReward
  • Creator subscriptions: tiers, offers (OfferByToken, OffersMyTiers, CreateSubscriptionOffer), subscribe/cancel/renew, trials (RedeemSubscriptionTrial)
  • Paid conversations: SubscribeToConversation, GrantFreeConversationAccess, GetConversationSubscriptionStats, GetMySubscriptionEarnings
  • Shop: product management, GetSellerProductsForProfile, PurchaseProductFromProfile, reviews, orders and disputes
  • Payouts and earnings: payout profile, StartPayoutOnboardingInline, cashout requests, MySalesInline, ad earnings, statements
  • Referrals: MyReferralInfoInline
  • Payment methods: keep the existing four operations, add CreatePaypalVaultSetupToken / ConfirmPaypalVaultSetupToken only if PayPal survives the StoreKit boundary decision
  • i18n + tests

Phase 11 — Calls polish

Only 4 operations missing; iOS leads web here. Small phase, do it whenever convenient.

  • ActiveCall, GetCall, MySpeakerRequests, CallStatusChanged
  • Backfill tests for the existing 23 Calls operations, which have none today

Testing requirements

The repo rule in the root CLAUDE.md — a feature is not done without tests, in both locales — applies to iOS too. Today apps/ios has tests for Auth only (8 files), and they may not even run.

Per phase, before ticking the last box:

  • Reducer tests for every new *Reducer (pure functions, cheapest coverage in the codebase)
  • Store tests with a mock repository for the async/side-effect paths
  • ViewModel tests for validation and error branches
  • Repository tests against a mocked GraphQLService, asserting the response mapping
  • Both en.lproj and es.lproj strings present — a screen that only reads correctly in English is not done
  • npm run codegen:ios clean, xcodebuild test -scheme app green

Infrastructure work, one-time:

  • <Testables> added to app.xcscheme (Phase 1)
  • macOS CI job running build + tests + codegen drift check (Phase 0)
  • Test target mirrors the feature structure: appTests/Tests/<Feature>Tests/{Data,Domain,Presentation} — still only AuthTests exists

Definition of done, per feature

A feature moves from - [ ] to - [x] only when all of these hold:

  1. Operations come from packages/graphql/operations/Web/** and codegen is committed
  2. The data path is View → ViewModel → Store → Repository → Service, with the repository registered in DIContainer and validated
  3. Strings exist in en.lproj and es.lproj
  4. Reducer, store and repository tests pass in CI
  5. Offline behaviour is deliberate — cached, queued, or explicitly unavailable
  6. The matching page under docs/features/ has its iOS checkbox ticked

Out of scope

Web-only by construction; no iOS equivalent is planned.

Web featureWhy
Static marketing and legal pages (/about, /blog, /press, /terms, …)Served in a webview or not at all
sitemap.ts, robots.ts, SSR metadata, the components/public/* browse-gate layer and every Public*Page variantSEO surface; iOS always uses the authenticated variants
Link-in-bio (/l/[username])A web landing page by definition
EarnLandingPage (/coins public)Marketing page
FloatingChatDesktop overlay pattern
Web push (lib/webPush, RegisterWebPushTokenInline)Replaced by APNs through the DeviceTokens domain, already at 100% on iOS
/payments/returnBrowser-redirect handler; StoreKit has no equivalent
settings/app-website-permissionsBrowser permission model
Google Analytics tagFirebase Analytics is already wired on iOS

Needs redesign rather than a port: every fiat purchase flow (StoreKit, Phase 10), crossposting OAuth (ASWebAuthenticationSession), and device permissions (native settings deep links).

Progress tracker

One box per domain. Tick when the domain's operations are wired, localized and tested.

  • AppVersion (1 operation)
  • Auth (45 remaining)
  • Calls (4 remaining)
  • Coins (37 remaining)
  • Contacts (6)
  • Conversations (20 remaining)
  • DeviceTokens (complete)
  • Family (7)
  • Follow (37 remaining)
  • Live (34)
  • Messages (83 remaining)
  • Misc (26)
  • Notes (4)
  • Notifications (28)
  • Payments (2 remaining)
  • Posts (126)
  • Profile (25 remaining)
  • Reports (3)
  • Search (41 remaining)
  • Settings (48)
  • Shop (16)
  • SocialLinks (6)
  • Stories (47)
  • Subscriptions (25)