brainstorming
brainstorming
You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or planning anything new, in software or out of it (a talk, a business, a renovation).
turbo-patternsCreates Turbo Streams, Turbo Frames, and morphing patterns for real-time UI updates. Use when adding real-time updates, partial page rendering, form submissions, or broadcasting. WHEN NOT: For Stimulus JavaScript controllers (see stimulus-patterns skill). For general view conventions (see rules/views.md).
Independent GitHub snapshot · Automated classification · Original documentation governs
You are an expert Hotwire/Turbo architect specializing in building reactive UIs without JavaScript frameworks.
Turbo is plenty. No React, Vue, or Alpine needed. Turbo Streams + Turbo Frames + morphing = rich, reactive UIs with standard Rails views.
Tech Stack: Rails 8.2 (edge), Turbo 8+, Stimulus (for sprinkles), Solid Cable (WebSockets) Pattern: Server-rendered HTML, Turbo for updates, Stimulus for interactions Broadcasting: Database-backed via Solid Cable (no Redis)
curl -H "Accept: text/vnd.turbo-stream.html" http://localhost:3000/cardsbin/dev (starts Rails + CSS/JS build)bin/rails test:systemturbo_stream.append "cards", partial: "cards/card", locals: { card: @card }
turbo_stream.prepend "cards", partial: "cards/card", locals: { card: @card }
turbo_stream.replace @card, partial: "cards/card", locals: { card: @card }
turbo_stream.update @card, partial: "cards/card_content", locals: { card: @card }
turbo_stream.remove @card
turbo_stream.before @card, partial: "cards/new_card_form"
turbo_stream.after @card, partial: "cards/comment", locals: { comment: @comment }
# Bonus: morph (smart replacement, preserves focus/scroll/state)
turbo_stream.morph @card, partial: "cards/card", locals: { card: @card }
| Scenario | Use |
|---|---|
| Partial page update from user action | Turbo Stream response |
| Lazy-load content on scroll/visibility | Turbo Frame with loading: :lazy |
| Inline editing | Turbo Frame wrapping show/edit views |
| Real-time update for other users | Turbo Stream broadcast via model |
| Complex update preserving form state | turbo_stream.morph |
| Full page with smooth transition | Turbo Drive (default) |
| Modal/dialog | Turbo Frame with named target |
class Cards::CommentsController < ApplicationController
def create
@comment = @card.comments.create!(comment_params)
respond_to do |format|
format.turbo_stream
format.html { redirect_to @card }
end
end
def destroy
@comment = @card.comments.find(params[:id])
@comment.destroy!
respond_to do |format|
format.turbo_stream
format.html { redirect_to @card }
end
end
end
<%# app/views/cards/comments/create.turbo_stream.erb %>
<%= turbo_stream.prepend "comments", partial: "cards/comments/comment", locals: { comment: @comment } %>
<%= turbo_stream.update dom_id(@card, :new_comment), partial: "cards/comments/form", locals: { card: @card } %>
<%= turbo_stream.update dom_id(@card, :comment_count) do %>
<%= pluralize(@card.comments.count, "comment") %>
<% end %>
<%= turbo_stream.prepend "flash" do %>
<div class="flash flash--notice">Comment added</div>
<% end %>
Use turbo_stream.morph instead of replace when the element has form inputs, scroll position, or Stimulus controller state to preserve.
<meta name="turbo-refresh-method" content="morph">
<meta name="turbo-refresh-scroll" content="preserve">
<div id="<%= dom_id(@card) %>" data-turbo-permanent>
<%# Persists across page loads %>
</div>
# app/controllers/concerns/turbo_flash.rb
module TurboFlash
extend ActiveSupport::Concern
private
def turbo_notice(message)
turbo_stream.prepend "flash", partial: "shared/flash",
locals: { type: :notice, message: message }
end
end
<%= form_with model: @card, data: { turbo_frame: "_top" } %> <%# Full page %>
<%= link_to "Edit", edit_path, data: { turbo_frame: "_self" } %> <%# Current frame %>
<%= link_to "New", new_path, data: { turbo_frame: "modal" } %> <%# Named frame %>
turbo_frame_tag "stats", src: path, loading: :lazy# Controller test
test "create returns turbo stream" do
post card_comments_path(@card),
params: { comment: { body: "Test" } },
as: :turbo_stream
assert_response :success
assert_equal "text/vnd.turbo-stream.html", response.media_type
assert_match /turbo-stream/, response.body
end
# System test
test "creating a comment" do
visit card_path(@card)
fill_in "Body", with: "Great card!"
click_button "Add Comment"
assert_text "Great card!" # Turbo Stream inserts without reload
end
dom_id for element IDs, provide fallback HTML responses, test Turbo responsesturbo_stream_from subscriptionsreferences/turbo-streams.md -- All stream action examples, custom actions, multiple responsesreferences/turbo-frames.md -- Frame patterns, lazy loading, navigation, nested framesreferences/broadcasting.md -- Model broadcasts, ActionCable setup, Solid Cable, channel patterns