[DX-475] Updated docs section for handling self published message#3215
[DX-475] Updated docs section for handling self published message#3215
Conversation
WalkthroughAdds a "Handle self-published messages" section to the chat messages docs describing two approaches to avoid duplicate UI for sent+received messages (wait for subscriber vs. optimistic UI with deduplication). Inserts multi-language examples and corrects Kotlin example type signatures; duplicates content across language blocks. Changes
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Pull request overview
This PR adds documentation for handling self-published messages in chat applications to prevent message duplication. When a message is sent using send(), the server echoes it back to all subscribers including the sender. This can cause messages to appear twice in the UI if not handled properly.
Changes:
- Added a new "Handle self-published messages" section with guidance on two approaches: only rendering from subscription (simple) or using serial-based deduplication for optimistic UI updates
- Provided code examples in JavaScript, React, Swift, Kotlin, and Android demonstrating the serial-based deduplication approach
- Included explanatory documentation about when to use each approach
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (1)
src/pages/docs/chat/rooms/messages.mdx (1)
238-240: Optional: add a code example for Approach 1.Approach 1 is described only in text. A minimal code snippet (subscribe-only rendering, no optimistic add) alongside Approach 2 would make the docs consistent and let developers copy-paste either pattern without having to infer what Approach 1 looks like in code.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/docs/chat/rooms/messages.mdx` around lines 238 - 240, Add a minimal code example to the "Approach 1" section showing the subscribe-only rendering pattern: show a snippet that subscribes to message events (e.g., onMessage or messagesSubscription handler), appends incoming messages to local state (e.g., setMessages or messagesRef), and calls send() without mutating the UI optimistically; reference the same send() function and the subscription listener used elsewhere in the doc so readers can copy-paste a simple pattern where send() only sends to the server and the subscription callback is solely responsible for adding messages to the local list.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/pages/docs/chat/rooms/messages.mdx`:
- Around line 233-241: Add a single clarifying sentence inside the existing
<Aside data-type='important'> block (near the explanation of send() and
subscribe()) stating that setting echoMessages=false does not disable the Chat
SDK's echo behavior—echoes are emitted by the Chat service itself and must be
deduplicated client-side (e.g., via serial or rendering only from
subscriptions). Reference the existing terms send(), subscribe(), serial, and
echoMessages=false so readers can immediately connect the caveat to the
surrounding guidance.
- Around line 297-314: The code mutates the shared var messages from two
concurrent contexts (the unstructured Task processing messagesSubscription and
the outer async scope) causing a data race; confine all mutations to the
MainActor by ensuring any append/contains on messages runs on the MainActor—wrap
the loop body inside the Task handling messagesSubscription so each iteration
does await MainActor.run { if !messages.contains(where: { $0.serial ==
message.serial }) { messages.append(message) } } and do the same for the
optimistic append (await MainActor.run { messages.append(sentMessage) }), or
alternatively annotate the surrounding UI/async scope (the function or enclosing
type that owns messages and invokes room.messages.send/write) with `@MainActor` so
both contexts perform mutations under actor isolation (reference: messages,
messagesSubscription, Task { ... }, and room.messages.send).
- Around line 262-263: The optimistic push after await room.messages.send(...)
is unconditional and can create duplicates; mirror the subscription's serial
check before adding the sent message: after calling room.messages.send(...) and
receiving message, verify the message.serial (or the same unique identifier used
by the subscription callback) is not already present in the messages collection
(e.g., check messages array for an existing entry with that serial) and only
call messages.push(message) when it's missing; apply this symmetric serial check
in the send-side code path that currently does messages.push(message).
---
Nitpick comments:
In `@src/pages/docs/chat/rooms/messages.mdx`:
- Around line 238-240: Add a minimal code example to the "Approach 1" section
showing the subscribe-only rendering pattern: show a snippet that subscribes to
message events (e.g., onMessage or messagesSubscription handler), appends
incoming messages to local state (e.g., setMessages or messagesRef), and calls
send() without mutating the UI optimistically; reference the same send()
function and the subscription listener used elsewhere in the doc so readers can
copy-paste a simple pattern where send() only sends to the server and the
subscription callback is solely responsible for adding messages to the local
list.
4e30b7c to
e1362a1
Compare
|
@coderabbitai review |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 1 out of 1 changed files in this pull request and generated 6 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
04bd9fc to
642602b
Compare
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (2)
src/pages/docs/chat/rooms/messages.mdx (2)
243-257:⚠️ Potential issue | 🟠 MajorOptimistic UI examples are missing send-side deduplication.
At Line 243 onward, the optimistic approach only shows subscriber-side duplicate checks. If you also append after
send(), you need the same serial/version guard on the send path to avoid duplicates in race conditions.Suggested pattern (apply across all language examples)
-const message = await room.messages.send({ text: 'hello' }); -myMessageList.push(message); +const message = await room.messages.send({ text: 'hello' }); +const exists = myMessageList.find( + (m) => m.serial === message.serial && m.version.serial === message.version.serial +); +if (!exists) { + myMessageList.push(message); +}In Ably Chat SDK docs, can `messages.send()` resolve before or after the same message is received by `messages.subscribe()` for the sender client?Also applies to: 259-321
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/docs/chat/rooms/messages.mdx` around lines 243 - 257, The optimistic UI flow currently only guards against duplicates in the messages.subscribe() handler; you must also perform the same serial/version deduplication immediately after messages.send() before appending to your local list to avoid race duplicates. In the code paths that call send() (the send handler) check the incoming message.serial and message.version.serial against your local store (e.g., myMessageList) and skip appending if a match exists, and ensure the shared list is a concurrent-safe structure or access is synchronized so both send() and the messages.subscribe() handler use the same guard logic.
233-234:⚠️ Potential issue | 🟠 MajorAdd the
echoMessages=falsecaveat explicitly in this section.This new guidance explains self-echo behavior, but it still does not state that Chat echo behavior cannot be disabled with
echoMessages=false, which is a key requirement of this PR objective.Suggested patch
When you send a message using `send()`, the server echoes it back to all subscribers in the room, including the sender. If your application adds the message to the UI immediately after calling `send()` and also appends it when received via `subscribe()`, the message will appear twice. There are two approaches to handle this. +In Chat, this self-echo behavior is the default and is not disabled by setting `echoMessages=false`.Does the Ably Chat SDK allow disabling sender echo with `echoMessages=false`, or is `echoMessages` only a Realtime SDK connection option?🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/docs/chat/rooms/messages.mdx` around lines 233 - 234, Add an explicit caveat after the paragraph about duplicate messages stating that echoMessages=false is a Realtime SDK connection option and does not disable Chat SDK sender echo: explain that when using Chat send() the server will echo messages back to all subscribers (including the sender) and that echoMessages=false cannot be used to prevent this behavior, referencing send(), subscribe(), and echoMessages=false so readers know which APIs are involved; update the paragraph that currently discusses the two approaches to handling duplicates to include this caveat and a short recommended action (e.g., deduplicate client-side by checking message ID or sender).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/pages/docs/chat/rooms/messages.mdx`:
- Around line 239-245: Two sentences use em dashes (—) in the new prose, which
violates doc rules; update the two occurrences around the discussion of the
subscriber and optimistic UI so they don't use em dashes: replace the em‑dash
between "the message list is only written to from a single place — the
subscriber" with a comma or parenthetical ("the subscriber") and replace the
em‑dash in "Because the message list is written to from two places — once after
`send()` and again inside the subscriber" with a comma or rephrase ("once after
`send()` and again inside the subscriber"); keep the references to `subscriber`,
`send()`, `serial`, and `version` unchanged.
- Around line 248-321: The code blocks use non-SDK language tags; update each
triple-backtick fence to the SDK-prefixed variants: change `javascript` ->
`realtime_javascript` for the room.messages.subscribe example, `react` ->
`realtime_react` for the useMessages/MyComponent snippet, `swift` ->
`realtime_swift` for the async messagesSubscription loop, `kotlin` ->
`realtime_kotlin` for the room.messages.subscribe lambda, and `android` ->
`realtime_android` for the Compose LaunchedEffect snippet so the <Code>
component follows the realtime_ SDK naming convention.
---
Duplicate comments:
In `@src/pages/docs/chat/rooms/messages.mdx`:
- Around line 243-257: The optimistic UI flow currently only guards against
duplicates in the messages.subscribe() handler; you must also perform the same
serial/version deduplication immediately after messages.send() before appending
to your local list to avoid race duplicates. In the code paths that call send()
(the send handler) check the incoming message.serial and message.version.serial
against your local store (e.g., myMessageList) and skip appending if a match
exists, and ensure the shared list is a concurrent-safe structure or access is
synchronized so both send() and the messages.subscribe() handler use the same
guard logic.
- Around line 233-234: Add an explicit caveat after the paragraph about
duplicate messages stating that echoMessages=false is a Realtime SDK connection
option and does not disable Chat SDK sender echo: explain that when using Chat
send() the server will echo messages back to all subscribers (including the
sender) and that echoMessages=false cannot be used to prevent this behavior,
referencing send(), subscribe(), and echoMessages=false so readers know which
APIs are involved; update the paragraph that currently discusses the two
approaches to handling duplicates to include this caveat and a short recommended
action (e.g., deduplicate client-side by checking message ID or sender).
ℹ️ Review info
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Disabled knowledge base sources:
- Jira integration is disabled
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (1)
src/pages/docs/chat/rooms/messages.mdx
| This approach has the advantage that the message list is only written to from a single place — the subscriber. This means you don't need a concurrent data structure or additional synchronization to protect the list from simultaneous writes. The tradeoff is a slight delay before the sent message appears in the UI, as it must complete a round trip to the server first. | ||
|
|
||
| #### Deduplicate with optimistic UI <a id="deduplicate-optimistic-ui"/> | ||
|
|
||
| If your application adds the message to the UI immediately after calling `send()` for a more responsive experience, you need to add a safety check in the subscriber to avoid duplicates. Validate the incoming message `serial` and `version` against existing messages. | ||
|
|
||
| Because the message list is written to from two places — once after `send()` and again inside the subscriber — you must use a concurrent or thread-safe data structure to handle simultaneous writes safely: |
There was a problem hiding this comment.
Replace em dashes in this new prose.
Line 239 and Line 245 use em dashes (—), which conflicts with the docs writing rules.
Suggested patch
-This approach has the advantage that the message list is only written to from a single place — the subscriber.
+This approach has the advantage that the message list is only written to from a single place, the subscriber.
-Because the message list is written to from two places — once after `send()` and again inside the subscriber — you must use a concurrent or thread-safe data structure to handle simultaneous writes safely:
+Because the message list is written to from two places, once after `send()` and again inside the subscriber, you must use a concurrent or thread-safe data structure to handle simultaneous writes safely:As per coding guidelines, "Avoid em-dashes, Latin abbreviations (use 'for example' not 'e.g.'), and slang in documentation writing."
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| This approach has the advantage that the message list is only written to from a single place — the subscriber. This means you don't need a concurrent data structure or additional synchronization to protect the list from simultaneous writes. The tradeoff is a slight delay before the sent message appears in the UI, as it must complete a round trip to the server first. | |
| #### Deduplicate with optimistic UI <a id="deduplicate-optimistic-ui"/> | |
| If your application adds the message to the UI immediately after calling `send()` for a more responsive experience, you need to add a safety check in the subscriber to avoid duplicates. Validate the incoming message `serial` and `version` against existing messages. | |
| Because the message list is written to from two places — once after `send()` and again inside the subscriber — you must use a concurrent or thread-safe data structure to handle simultaneous writes safely: | |
| This approach has the advantage that the message list is only written to from a single place, the subscriber. This means you don't need a concurrent data structure or additional synchronization to protect the list from simultaneous writes. The tradeoff is a slight delay before the sent message appears in the UI, as it must complete a round trip to the server first. | |
| #### Deduplicate with optimistic UI <a id="deduplicate-optimistic-ui"/> | |
| If your application adds the message to the UI immediately after calling `send()` for a more responsive experience, you need to add a safety check in the subscriber to avoid duplicates. Validate the incoming message `serial` and `version` against existing messages. | |
| Because the message list is written to from two places, once after `send()` and again inside the subscriber, you must use a concurrent or thread-safe data structure to handle simultaneous writes safely: |
🧰 Tools
🪛 LanguageTool
[style] ~239-~239: Three successive sentences begin with the same word. Consider rewording the sentence or use a thesaurus to find a synonym.
Context: ...o from a single place — the subscriber. This means you don't need a concurrent data ...
(ENGLISH_WORD_REPEAT_BEGINNING_RULE)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/pages/docs/chat/rooms/messages.mdx` around lines 239 - 245, Two sentences
use em dashes (—) in the new prose, which violates doc rules; update the two
occurrences around the discussion of the subscriber and optimistic UI so they
don't use em dashes: replace the em‑dash between "the message list is only
written to from a single place — the subscriber" with a comma or parenthetical
("the subscriber") and replace the em‑dash in "Because the message list is
written to from two places — once after `send()` and again inside the
subscriber" with a comma or rephrase ("once after `send()` and again inside the
subscriber"); keep the references to `subscriber`, `send()`, `serial`, and
`version` unchanged.
b888e03 to
d6272d8
Compare
…es with pros and cons
d6272d8 to
8d81ee8
Compare
|
|
||
| This approach has the advantage that the message list is only written to from a single place — the subscriber. This means you don't need a concurrent data structure or additional synchronization to protect the list from simultaneous writes. The tradeoff is that the sent message must complete a round trip to the server before appearing in the UI. While Ably's realtime delivery is always near-instantaneous, this may introduce a slight delay rarely in poor network conditions. | ||
|
|
||
| #### Deduplicate with optimistic UI <a id="deduplicate-optimistic-ui"/> |
There was a problem hiding this comment.
If chat REST API starts respecting echoMessages=false as per https://ably.atlassian.net/browse/DX-456, section Deduplicate with optimistic UI will only have a config. echoMessages=false and a note to use concurrent or thread-safe data structure to handle simultaneous writes safely.
|
|
||
| #### Wait for the subscriber <a id="wait-for-subscriber"/> | ||
|
|
||
| The recommended approach is to not add the message to the UI immediately before/after calling `send()`. Instead, only append messages to the UI inside the `subscribe()` listener. Since the server echoes every sent message back to the sender as a subscriber event, the message will still appear in the UI when it arrives through the subscription. This eliminates the duplication problem entirely and requires no deduplication logic in the subscriber. |
There was a problem hiding this comment.
Users still have to contend with cross-region publishes, so they still need to handle ordering. They also need to apply updates/deletes, which means existence checks. At this point, the deduplication logic is essentially mostly implemented (and we recommend to use the .with() function to handle this in JS land).
I agree with handling concurrent writes to a data structure being a concern, but I don't think this means we should recommend in all cases for non-optimistic updates.
There was a problem hiding this comment.
The cases where we recommend non-optimistic are updates/deletes and annotations.
Deduplicate with optimistic UIsection can be updated withechoMessages=falseif https://ably.atlassian.net/browse/DX-456 is fixed.Summary by CodeRabbit