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.
| Domain | Operations | web | iOS | iOS coverage |
|---|---|---|---|---|
| AppVersion | 1 | 0 | 0 | 0% |
| Auth | 51 | 45 | 6 | 11% |
| Calls | 27 | 5 | 23 | 85% |
| Coins | 41 | 40 | 4 | 9% |
| Contacts | 6 | 6 | 0 | 0% |
| Conversations | 44 | 15 | 24 | 54% |
| DeviceTokens | 2 | 0 | 2 | 100% |
| Family | 7 | 7 | 0 | 0% |
| Follow | 39 | 39 | 2 | 5% |
| Live | 34 | 34 | 0 | 0% |
| Messages | 89 | 65 | 6 | 6% |
| Misc | 26 | 25 | 0 | 0% |
| Notes | 4 | 4 | 0 | 0% |
| Notifications | 28 | 24 | 0 | 0% |
| Payments | 6 | 6 | 4 | 66% |
| Posts | 126 | 124 | 0 | 0% |
| Profile | 28 | 24 | 3 | 10% |
| Reports | 3 | 3 | 0 | 0% |
| Search | 42 | 41 | 1 | 2% |
| Settings | 48 | 48 | 0 | 0% |
| Shop | 16 | 16 | 0 | 0% |
| SocialLinks | 6 | 6 | 0 | 0% |
| Stories | 47 | 47 | 0 | 0% |
| Subscriptions | 25 | 25 | 0 | 0% |
| Total | 746 | 649 | 75 | 10% |
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,RequestPhoneOtpMutationandSearchConversationsQueryall still exist in the package, but web has moved to newer*Inlinedocuments (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.
Restore the Swift package link
The generated module was renamed ClosegramAPI → ClosegramGraphQL (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
ClosegramAPIpackage product dependency withClosegramGraphQLin theapptarget and commitproject.pbxproj— done;project.pbxprojlinksClosegramGraphQL(0 remaining references to the old product name) - Confirm
xcodebuild -scheme app buildsucceeds from a clean derived-data state — reconfirmed after the Phase 1 NavigationStack rewrite (see below) - Update the stale
ClosegramAPIreferences 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 references | Backend status | Resolution |
|---|---|---|
ChangePasswordMutation | Field changePassword exists | Adopt ChangePasswordInline (Auth/ChangePasswordInline.graphql) → ChangePasswordInlineMutation |
ResetPasswordMutation | Field resetPassword exists | Adopt ResetPasswordInline (Auth/ResetPasswordInline.graphql) → ResetPasswordInlineMutation |
LoginWithFirebaseMutation | Field removed from the schema | Migrate to loginWithIdToken (Auth/LoginWithIdToken.graphql) — this is the Google/Firebase path web uses |
DeleteAccountMutation | Field deleteAccount(password: String!) exists, no shared document | Add a document under Auth/ or adopt the newer flow web uses: RequestAccountDeletionInline + CancelAccountDeletionInline + DeleteAccountImmediatelyInline (Settings) |
MyPurchaseHistoryQuery | Field myPurchaseHistory exists | Resolved — rather than switching to MyCoinPurchases/MyRecentTransactions, a MyPurchaseHistory($limit, $offset) document was added to Coins/MyCoinPurchases.graphql and codegen regenerated; iOS resolves it directly |
SearchMyMessagesQuery | Field searchMyMessages exists, no shared document | Adopt SearchMessagesInline (Messages/SearchMessagesInline.graphql), which is what web uses |
RefreshTokenMutation | No such mutation — refreshToken is only a String field on the auth response | Redesign 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 rootCLAUDE.md, mobile-only operations still live underWeb/, shared withapollo-webandapollo-swift) - Rewrite the seven call sites in Swift
- Run
npm run codegen:iosand commit the regeneratedpackages/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 intoci.yml, which only covers backend/frontend), moved there after being found to live at the typo'd path.githubtest/workflows/ios-ci.ymland never actually run on GitHub Actions. Also fixed staleXCODE_VERSION: '16.1'/macos-15runners, neither of which support this project'sIPHONEOS_DEPLOYMENT_TARGET = 26.1 - Same job builds the
appscheme and runsappTests(see Testing) - Document the refresh ritual in iOS App: pull →
npm run codegen:schema→npm 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.
-
MeQuery→GetMe/GetMeProfileInline -
GetUserQuery→GetPublicProfile -
UpdateProfileMutation→UpdateProfileInline -
LoginWithAppleMutation→LoginWithAppleInline -
LoginWithPhoneMutation→LoginWithPhoneInline -
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/CallRepositoryand stopCallStore(+ its five extensions) from callingGraphQLService/ChatAPIServicedirectly — 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 defaultinitarguments) — 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,CreateDirectChatViewandCreateGroupChatView(plus, before it was deleted above,NestedNavigationView'sshowCreateChat()) each didlet 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 resolveDIContainer.shared.resolve(SearchStore.self). This is a Store-level fix, not the Repository-level gap the two bullets above still track
Test seams
- Move
MockAuthRepositoryandMockChatRepositoryout of the app target (or wrap in#if DEBUG) — they currently ship in the production binary - Delete the duplicate:
MockAuthRepositoryexists twice, inFeatures/Auth/Data/Repositories/(2.5 KB) andappTests/Tests/AuthTests/Data/(7.4 KB), with divergent behaviour — still open, both still exist - Add
<Testables>forappTeststo the sharedapp.xcscheme— done.TestActionhadshouldAutocreateTestPlan = "YES"but no<Testables>block, soxcodebuild test/fastlane testfailed 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 inAuthTests(real gaps inLoginUseCase/RegisterUseCasevalidation, plus 3 tests asserting the wrong password-strength tier), all fixed for real
Structure
- Replace the
NestedNavigationView/CustomTabBarControllerUIKit bridge (aUIViewControllerRepresentablewrappingUINavigationController/UITabBarController, manually reconstructing aUIHostingControllerwith a hand-picked.environmentObject()list per pushed route) with a pure-SwiftUIMainTabView: an outerNavigationStack(path: $router.path)around aTabView, each of the 5 tabs with its own innerNavigationStack,.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.swiftdeleted 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/andStores/folders — none are part of the declared architecture - Decide the fate of
Domain/UseCases: it exists only inAuth. 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 targetClosegrammAppwhoseSources/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.swiftand move both fallbacks to xcconfig/.env - Correct
apps/ios/README.md, which documents aCacheCoordinator,ApolloClientManager,GoogleAuthManager,Core/Models/User.swift,Views/Messages/and aTESTING_GUIDE.mdthat 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 removedloginWithFirebase) - Apple sign-in via
LoginWithAppleInline; phone OTP viaLoginWithPhoneInline+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
-
Logoutwired to clear Keychain, Apollo cache and device tokens - i18n strings in
en.lprojandes.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*FromProfileblock/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
Contactsframework + 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:
DeleteMessageForMevsDeleteMessageForEveryone(iOS has only the legacyDeleteMessage) - In-conversation search:
SearchMessagesInline(replaces the deadSearchMyMessagesQuery) - 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:
NotificationAddedsubscription - 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
Settingsto 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
DataExportStatusChangedInlinesubscription - 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:
CheckAppVersionat 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/CapturePaypalCoinOrderoperations 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/ConfirmPaypalVaultSetupTokenonly 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.lprojandes.lprojstrings present — a screen that only reads correctly in English is not done -
npm run codegen:iosclean,xcodebuild test -scheme appgreen
Infrastructure work, one-time:
-
<Testables>added toapp.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 onlyAuthTestsexists
Definition of done, per feature
A feature moves from - [ ] to - [x] only when all of these hold:
- Operations come from
packages/graphql/operations/Web/**and codegen is committed - The data path is
View → ViewModel → Store → Repository → Service, with the repository registered inDIContainerand validated - Strings exist in
en.lprojandes.lproj - Reducer, store and repository tests pass in CI
- Offline behaviour is deliberate — cached, queued, or explicitly unavailable
- The matching page under
docs/features/has its iOS checkbox ticked
Out of scope
Web-only by construction; no iOS equivalent is planned.
| Web feature | Why |
|---|---|
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 variant | SEO surface; iOS always uses the authenticated variants |
Link-in-bio (/l/[username]) | A web landing page by definition |
EarnLandingPage (/coins public) | Marketing page |
FloatingChat | Desktop overlay pattern |
Web push (lib/webPush, RegisterWebPushTokenInline) | Replaced by APNs through the DeviceTokens domain, already at 100% on iOS |
/payments/return | Browser-redirect handler; StoreKit has no equivalent |
settings/app-website-permissions | Browser permission model |
| Google Analytics tag | Firebase 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)