Repository navigation
feat: add real time updates - #13833
Conversation
cf537a8 to
14d7539
Compare
🎉 Beta Versions Generated (commit:
|
…reshes
- Gate the subscription on useIsFocusedInTab("inbox") so it doesn't keep
refetching the conversations connection while the user is on another
bottom tab (isActiveTab alone doesn't reflect tab focus).
- Track a pendingRefresh flag so an event landing while refetchConnection
is already in flight triggers a follow-up refresh instead of being
silently dropped.
- Hoist the duplicated onEvent/onConnected body into one refreshInbox fn.
Addresses PR review feedback on #13833.
🎉 Beta Versions Generated (commit:
|
Subscribe to Gravity's ConversationsChannel (over the existing app-wide ActionCable connection) while the inbox tab or a conversation is open. The channel broadcasts a minimal signal when Impulse delivers a message; in response we refetch the affected Relay connections and refresh the unread badge, so new messages appear without pull-to-refresh. - useConversationsWebsocket: screen-scoped channel subscription hook, gated by the AREnableConversationsRealtime feature flag (off by default) and the user's session token - Conversation screen refetches when an event matches the open thread - Inbox list refetches on any event while the tab is active Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YQB2YVTL96ynGhYNTgbz2d
…annels fix
The mock had `channels: {}` on the cable mock instead of the
channelsHolder mock, matching the pre-fix code that incorrectly
referenced `cable.channels`. CI was failing because the real fix
(delete channelsHolder.channels[channelKey]) now throws against this
stale mock shape.
- track last marked message id so messages arriving on an open thread still get marked as read - debounce received websocket events (leading + trailing) to collapse bursts into at most two refetches - refetch on socket reconnect via a new onConnected callback so nothing broadcast during a drop is lost - unsubscribe the raw subscription if setChannel returns nothing - memoize message grouping in Messages and the partner offer event so the FlatList data prop keeps a stable identity - document why from_principal events are not filtered out Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Deliver received events directly; keeping the behavior simple is preferred over collapsing bursts. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Assisted-by: Claude:Fable-5 [claude-code]
…reshes
- Gate the subscription on useIsFocusedInTab("inbox") so it doesn't keep
refetching the conversations connection while the user is on another
bottom tab (isActiveTab alone doesn't reflect tab focus).
- Track a pendingRefresh flag so an event landing while refetchConnection
is already in flight triggers a follow-up refresh instead of being
silently dropped.
- Hoist the duplicated onEvent/onConnected body into one refreshInbox fn.
Addresses PR review feedback on #13833.
Assisted-by: Claude:Sonnet-5 [claude-code]
- Hoist hasConnectedOnce to a ref so a resubscribe (enabled toggled with screen focus) fires onConnected as a catch-up refetch instead of swallowing it as the initial connection. This also covers refreshing the inbox when the user returns to the tab. - Log rejected subscriptions so a broken rollout is visible. - Gate the conversation subscription on useIsFocused so a hidden screen doesn't refetch and silently mark unseen messages as read; refresh the unread badge for events from other conversations too. - Refetch the inbox connection at its loaded count so a paginated list isn't truncated back to the first page on every event. - Drain pendingRefresh after loadMore as well, not just refetchConnection. Addresses PR review feedback on #13833.
…read The oldest loaded message group always rendered the artwork/show preview above it, so the preview appeared mid-thread while older pages were still loading. Gate it on relay.hasMore() so it only shows once there is nothing older left to fetch; until then the group renders like any other (with the loading indicator above it while paginating). Also bump the messages page size from 10 to 20.
Switching between staging and production left realtime dead until the app was restarted. The effect that creates the ActionCable consumer ran on `isStaging` but guarded its body with `if (!actionCable)`, so on an env switch it saw the existing consumer and never created one for the new host. Its cleanup read `actionCable` from state, which the creating run captured as null, so the old consumer was never disconnected either — the socket leaked on plain unmount too. Key the effect on `wssUrl` and disconnect the cable created in that same run. Consumers already have `cable` in their dep arrays, so they resubscribe against the new host on their own.
…iOS) A message arriving while the user is reading that same conversation showed a banner over the thread they were already looking at. The websocket already adds the message inline, so the banner is noise. Native has to decide this synchronously inside willPresentNotification, and it can't ask JS anything at that point, so React publishes the visible conversation instead: onStateChange writes `visibleConversationID` into the existing reactState channel on every navigation, and empties it whenever no conversation is on screen. The delegate reads it back, and when the push's deep link points at the same conversation it presents nothing instead of an alert. The payload is still forwarded to React, so the unread badge keeps refreshing. Native state is only consulted for conversation links, which also keeps the existing strict mock in AppDelegate+NotificationsTests happy. `reactStateUpdated` only accepted the full environment object; its keys are now optional, which is what native already assumed since it merges the dictionary. Android no-ops that method, so the shared call is safe there. Suppressing the Android banner is a separate change. Assisted-by: Claude:Opus-5 [claude-code]
…Android) The Android counterpart to the iOS change. `messaging().onMessage` only fires in the foreground and is what calls `notifee.displayNotification`, so skipping that call is all it takes — no native code and no state pushed down, because JS can read the focused route directly when the message arrives. Uses the same two rules as iOS: the push has to point at a conversation, and that conversation has to be the focused route. Only reaches pushes that fall through to React Native Firebase. Braze displays its own notifications natively in BrazePushListenerService, which never hits this handler.
2b8d56a to
714cc90
Compare
Code ReviewSummaryAdds real-time conversation updates over Gravity's ActionCable websocket behind the The hook lifecycle (feature flag + auth + focus gating, ref-held callbacks, Issues Found🟡 Important — env switch can leave live-auction sockets dead ( 🟢 Suggestion — PR checklist says "No UI changes", but there are a few
Areas Reviewed
Questions for Author
|
| params?: object | undefined | ||
| }): string => { | ||
| if (route?.name !== "Conversation") { | ||
| return "" |
There was a problem hiding this comment.
Nitpick: Shouldn't it, in this case, return a null instead to clearly indicate there's no ID?
There was a problem hiding this comment.
good question, the reason behind this is to align the default value inside ios native modules with the one in react-native. happy to change this if you feel strong about it.
|
Merging this now right after code freeze to get 2 weeks of testing this |
|
Fwiw, it already passed QA 🎉 |
Description
This PR adds real-time conversation updates using websockets by introducing a new
useConversationsWebsockethook that subscribes to Gravity's ConversationsChannel. When messages are delivered to the user's conversations, the hook triggers a refetch of conversation data and updates the unread count.Key changes:
useConversationsWebsockethook that manages websocket subscriptions to conversation eventsGravityWebsocketContextfor cable managementAREnableConversationsRealtimeto allow safe rolloutImplementation details:
subscriptionKeyto allow multiple simultaneous subscriptions (e.g., inbox list + open conversation)Screen.Recording.2026-07-15.at.16.19.03.mov
PR Checklist
AREnableConversationsRealtimeuseConversationsWebsocketChangelog updates
Changelog updates
Dev changes
useConversationsWebsockethookhttps://claude.ai/code/session_01YQB2YVTL96ynGhYNTgbz2d