diff --git a/docs/guides/rails.md b/docs/guides/rails.md index 424b819db..6863ec1fa 100644 --- a/docs/guides/rails.md +++ b/docs/guides/rails.md @@ -579,6 +579,108 @@ This setup allows for: 2. Background processing to prevent request timeouts 3. Automatic persistence of all messages and tool calls +### Handling Message Ordering with ActionCable + +ActionCable does not guarantee message order due to its concurrent processing model. Messages are distributed to worker threads that deliver them to clients concurrently, which can cause out-of-order delivery (e.g., assistant responses appearing above user messages). Here are the recommended solutions: + +#### Option 1: Client-Side Reordering with Stimulus (Recommended) + +Add a Stimulus controller that maintains correct chronological order based on timestamps. This example demonstrates the concept - adapt it to your specific needs: + +```javascript +// app/javascript/controllers/message_ordering_controller.js +// Note: This is an example implementation. Test thoroughly before production use. +import { Controller } from "@hotwired/stimulus" + +export default class extends Controller { + static targets = ["message"] + + connect() { + this.reorderMessages() + this.observeNewMessages() + } + + observeNewMessages() { + // Watch for new messages being added to the DOM + const observer = new MutationObserver((mutations) => { + let shouldReorder = false + + mutations.forEach((mutation) => { + mutation.addedNodes.forEach((node) => { + if (node.nodeType === 1 && node.matches('[data-message-ordering-target="message"]')) { + shouldReorder = true + } + }) + }) + + if (shouldReorder) { + // Small delay to ensure all attributes are set + setTimeout(() => this.reorderMessages(), 10) + } + }) + + observer.observe(this.element, { childList: true, subtree: true }) + this.observer = observer + } + + disconnect() { + if (this.observer) { + this.observer.disconnect() + } + } + + reorderMessages() { + const messages = Array.from(this.messageTargets) + + // Sort by timestamp (created_at) + messages.sort((a, b) => { + const timeA = new Date(a.dataset.createdAt).getTime() + const timeB = new Date(b.dataset.createdAt).getTime() + return timeA - timeB + }) + + // Reorder in DOM + messages.forEach((message) => { + this.element.appendChild(message) + }) + } +} +``` + +Update your views to use the controller: + +```erb +<%# app/views/chats/show.html.erb %> + +