Chat room

Action + live broadcast over Turbo Streams (same-process async cable in this demo).

ruby
Welcome to the phlex-reactive chat demo!
you
Send a message — it broadcasts with zero custom JS.
# The composer's send_message broadcasts the new message to every
# subscriber, excluding the sender's own connection:
render ChatRoomComponent.new(room: "lobby", messages: ChatMessage.for_room("lobby"))
# frozen_string_literal: true

# The chat room: subscribes to the room's Turbo Stream and renders the existing
# messages + the composer. New messages arrive via broadcast (see
# ChatComposerComponent#send_message), appended live to #chat-messages.
#
# NOTE: this demo runs on the in-process async Action Cable adapter, so live
# updates are same-process. True cross-tab/cross-client delivery needs a real
# pub/sub backend (Redis or pgbus) — out of scope for the demo site.
class ChatRoomComponent < Phlex::HTML
  include Phlex::Rails::Helpers::TurboStreamFrom

  def initialize(room: 'lobby', messages: [])
    @room = room
    @messages = messages
  end

  def view_template
    div(class: 'flex flex-col gap-3') do
      turbo_stream_from(*ChatMessage.stream_key(@room))

      div(id: "chat-messages-#{@room}",
          class: 'flex flex-col gap-1 min-h-32 max-h-80 overflow-y-auto',
          data: { testid: 'chat-messages' }) do
        @messages.each { render ChatMessageComponent.new(chat_message: it) }
      end

      render ChatComposerComponent.new(room: @room)
    end
  end
end