Fix pending invite stuck after navigation#514
Conversation
3f67e9c to
580d8e7
Compare
|
Claude finished @yewreeka's task —— View job Code Review — Fix Pending Invite Stuck After NavigationI've reviewed the changes and overall this is a solid fix for the stuck invite problem. The three-pronged approach (post-sync discovery, resume re-sync, and observation restart) effectively addresses the root causes. However, there are some potential issues and improvements worth considering. Code Quality & Best Practices✅ Strong Points
|
580d8e7 to
19353bc
Compare
34d77c2 to
e6abb51
Compare
Merge activity
|
## Problem
When a joiner scans an invite and navigates away before the inviter approves, the conversation permanently stays as a "pending invite" and never transitions to an active conversation. This was reported by Andy Yang — stuck for 45+ minutes on "Balloon Animals".
## Root Cause
Three gaps in the sync/stream lifecycle:
1. **No post-sync group discovery** — after syncing, the app never checked for XMTP groups that were added while streams were stopped
2. **Resume didn't re-sync** — `handleResume()` only restarted streams without re-syncing, missing groups added during pause
3. **ValueObservation cancelled on navigation** — the DB observation watching for the draft→active transition was tied to the conversation view's lifecycle
## Fix
### 1. Post-sync group discovery (`SyncingManager`)
New `discoverNewConversations()` method lists all XMTP groups after every sync and processes any missing from the local DB. Acts as a safety net for missed stream events.
### 2. Re-sync on resume (`SyncingManager`)
`handleResume()` now calls `syncAllConversations` + `discoverNewConversations` + `processJoinRequestsAfterSync` instead of only restarting streams.
### 3. Restart observation on navigation (`ConversationStateMachine`)
`handleUseExisting()` checks if the conversation is still a draft with an inviteTag and restarts `waitForJoinedConversation` observation in the background.
### 4. Accessibility fix
Added `accessibilityIdentifier("close-new-conversation")` to the close button on `NewConversationView`.
## Testing
- 59 unit tests pass
- QA test 24 (pending invite recovery) passed end-to-end:
- Joiner scans invite → navigates away → inviter approves via CLI → conversation automatically transitions to active → messaging works
## Files Changed
- `ConvosCore/Sources/ConvosCore/Syncing/SyncingManager.swift` — post-sync discovery + resume re-sync
- `ConvosCore/Sources/ConvosCore/Inboxes/ConversationStateMachine.swift` — restart pending invite observation
- `Convos/Conversation Creation/NewConversationView.swift` — close button accessibility ID
- `docs/plans/fix-pending-invite-stuck.md` — plan doc
- `qa/tests/24-pending-invite-recovery.md` — QA test
<!-- Macroscope's pull request summary starts here -->
<!-- Macroscope will only edit the content between these invisible markers, and the markers themselves will not be visible in the GitHub rendered markdown. -->
<!-- If you delete either of the start / end markers from your PR's description, Macroscope will append its summary at the bottom of the description. -->
> [!NOTE]
> ### Recover pending invites after navigation by observing draft `inviteTag` in `ConversationStateMachine.handleUseExisting` and backfilling missed conversations in `SyncingManager`
> Add background observation for draft invites in `ConversationStateMachine` to transition to the joined conversation, and backfill missed conversations on sync and resume in `SyncingManager`; also add a close button accessibility identifier in the new conversation view. Key entry points: [`ConvosCore/Sources/ConvosCore/Inboxes/ConversationStateMachine.swift`](https://github.com/xmtplabs/convos-ios/pull/514/files#diff-a1762ce564035d6cdd7e3b2d3200cfe0db6c3ec5fac03636392780086ba58344), [`ConvosCore/Sources/ConvosCore/Syncing/SyncingManager.swift`](https://github.com/xmtplabs/convos-ios/pull/514/files#diff-ba2362cf646b78283a1af17654ea8d2c18f6b1cb686a78e7761e10f1b0328101), [`Convos/Conversation Creation/NewConversationView.swift`](https://github.com/xmtplabs/convos-ios/pull/514/files#diff-be8eeec29bfb3f245f16cd6258d1ef28ced1aa75bad52187e34243f915b084f6).
>
> #### 📍Where to Start
> Start with `ConversationStateMachine.handleUseExisting` and the new helpers in [ConversationStateMachine.swift](https://github.com/xmtplabs/convos-ios/pull/514/files#diff-a1762ce564035d6cdd7e3b2d3200cfe0db6c3ec5fac03636392780086ba58344), then review `SyncingManager.handleSyncComplete` and `handleResume` plus `discoverNewConversations` in [SyncingManager.swift](https://github.com/xmtplabs/convos-ios/pull/514/files#diff-ba2362cf646b78283a1af17654ea8d2c18f6b1cb686a78e7761e10f1b0328101).
>
> <!-- Macroscope's review summary starts here -->
>
> <sup><a href="https://app.macroscope.com">Macroscope</a> summarized e6abb51.</sup>
> <!-- Macroscope's review summary ends here -->
>
<!-- macroscope-ui-refresh -->
<!-- Macroscope's pull request summary ends here -->
e6abb51 to
d422b19
Compare
|
|
||
| // Cancel observation tasks and stop accepting new messages | ||
| // Note: currentTask is already cancelled by delete() - don't cancel ourselves! | ||
| messageStreamContinuation?.finish() |
There was a problem hiding this comment.
🟡 Medium Inboxes/ConversationStateMachine.swift:755
Cleanup doesn’t fully reset message‑sending state. Consider clearing cachedMessageWriter during teardown (e.g., in handleDelete()/cleanUpState()) and resetting isMessageStreamSetup synchronously so a quick restart doesn’t skip stream setup or reuse a stale writer.
await clearInviteJoinErrorHandler()
+ cachedMessageWriter = nil
messageStreamContinuation?.finish()🚀 Reply "fix it for me" or copy this AI Prompt for your agent:
In file ConvosCore/Sources/ConvosCore/Inboxes/ConversationStateMachine.swift around lines 755-756:
Cleanup doesn’t fully reset message‑sending state. Consider clearing `cachedMessageWriter` during teardown (e.g., in `handleDelete()`/`cleanUpState()`) and resetting `isMessageStreamSetup` synchronously so a quick restart doesn’t skip stream setup or reuse a stale writer.
Evidence trail:
ConvosCore/Sources/ConvosCore/Inboxes/ConversationStateMachine.swift lines 93, 198-202, 205-221, 744-774, 861-868 at REVIEWED_COMMIT. `cachedMessageWriter` is declared at line 93, set at 219, but never cleared in `handleDelete()` or `cleanUpState()`. `resetMessageStream()` (lines 198-202) resets `isMessageStreamSetup` but is only called asynchronously via `onTermination` (line 182) or task completion (line 194), not synchronously in cleanup functions.
PR #514 added syncAllConversations on resume to pick up changes while paused/backgrounded, but the test still asserted it should not be called. Update to match current behavior.
PR #514 added syncAllConversations on resume to pick up changes while paused/backgrounded, but the test still asserted it should not be called. Update to match current behavior.
Problem
When a joiner scans an invite and navigates away before the inviter approves, the conversation permanently stays as a "pending invite" and never transitions to an active conversation. This was reported by Andy Yang — stuck for 45+ minutes on "Balloon Animals".
Root Cause
Three gaps in the sync/stream lifecycle:
handleResume()only restarted streams without re-syncing, missing groups added during pauseFix
1. Post-sync group discovery (
SyncingManager)New
discoverNewConversations()method lists all XMTP groups after every sync and processes any missing from the local DB. Acts as a safety net for missed stream events.2. Re-sync on resume (
SyncingManager)handleResume()now callssyncAllConversations+discoverNewConversations+processJoinRequestsAfterSyncinstead of only restarting streams.3. Restart observation on navigation (
ConversationStateMachine)handleUseExisting()checks if the conversation is still a draft with an inviteTag and restartswaitForJoinedConversationobservation in the background.4. Accessibility fix
Added
accessibilityIdentifier("close-new-conversation")to the close button onNewConversationView.Testing
Files Changed
ConvosCore/Sources/ConvosCore/Syncing/SyncingManager.swift— post-sync discovery + resume re-syncConvosCore/Sources/ConvosCore/Inboxes/ConversationStateMachine.swift— restart pending invite observationConvos/Conversation Creation/NewConversationView.swift— close button accessibility IDdocs/plans/fix-pending-invite-stuck.md— plan docqa/tests/24-pending-invite-recovery.md— QA testNote
Recover pending invites after navigation by observing draft
inviteTaginConversationStateMachine.handleUseExistingand backfilling missed conversations inSyncingManagerAdd background observation for draft invites in
ConversationStateMachineto transition to the joined conversation, and backfill missed conversations on sync and resume inSyncingManager; also add a close button accessibility identifier in the new conversation view. Key entry points:ConvosCore/Sources/ConvosCore/Inboxes/ConversationStateMachine.swift,ConvosCore/Sources/ConvosCore/Syncing/SyncingManager.swift,Convos/Conversation Creation/NewConversationView.swift.📍Where to Start
Start with
ConversationStateMachine.handleUseExistingand the new helpers in ConversationStateMachine.swift, then reviewSyncingManager.handleSyncCompleteandhandleResumeplusdiscoverNewConversationsin SyncingManager.swift.Macroscope summarized d422b19.