Help us improve
Share bugs, ideas, or general feedback.
From majestic-rails
Implements Hotwire features with Turbo Drive, Turbo Frames, and Turbo Streams in Rails 8, covering morphing, broadcasts, lazy loading, and real-time updates.
npx claudepluginhub majesticlabs-dev/majestic-marketplace --plugin majestic-railsHow this skill is triggered — by the user, by Claude, or both
Slash command
/majestic-rails:hotwire-coderThis skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
Hotwire sends HTML over the wire instead of JSON:
Guides building reactive Rails apps with Hotwire (Turbo Drive/Frames/Streams, Stimulus): installation, ActionCable/Redis setup, core patterns.
Implements Hotwire Turbo (Drive, Frames, Streams, Morph) and Stimulus controllers in Rails views for SPA-like interactivity, real-time updates, and progressive enhancement.
Implements push-driven Hotwire behavior: Turbo Streams over WebSocket/SSE, custom stream actions, inline stream tags, live list updates, cross-tab sync.
Share bugs, ideas, or general feedback.
Hotwire sends HTML over the wire instead of JSON:
<body> without full page reloads<%= link_to "Download PDF", report_path(format: :pdf), data: { turbo: false } %>
<%= form_with model: @legacy, data: { turbo: false } do |f| %>
def create
@post = Post.new(post_params)
@post.save ? redirect_to(@post, notice: "Created!") : render(:new, status: :unprocessable_entity)
end
<turbo-frame id="comments">
<%= render @post.comments %>
<%= link_to "Load More", post_comments_path(@post, page: 2) %>
</turbo-frame>
<turbo-frame id="notifications" src="<%= notifications_path %>" loading="lazy">
<p>Loading...</p>
</turbo-frame>
See references/lazy-loading.md for skeleton UI patterns, infinite scroll, and Stimulus loading controllers.
<%= link_to "View Post", post_path(@post), data: { turbo_frame: "_top" } %>
<%= link_to "Results", search_path, data: { turbo_frame: "results" } %>
<turbo-frame id="<%= dom_id(post) %>">
<article>
<h2><%= post.title %></h2>
<%= link_to "Edit", edit_post_path(post) %>
</article>
</turbo-frame>
<%= turbo_stream.append "comments" do %><%= render @comment %><% end %>
<%= turbo_stream.replace dom_id(@post) do %><%= render @post %><% end %>
<%= turbo_stream.remove dom_id(@comment) %>
def create
@comment = @post.comments.create(comment_params)
respond_to do |format|
format.turbo_stream # renders create.turbo_stream.erb
format.html { redirect_to @post }
end
end
class Comment < ApplicationRecord
after_create_commit -> {
broadcast_append_to post, target: "comments", partial: "comments/comment"
}
after_update_commit -> { broadcast_replace_to post }
after_destroy_commit -> { broadcast_remove_to post }
end
<%= turbo_stream_from @post %>
<div id="comments"><%= render @post.comments %></div>
Note: For LLM streaming or features requiring message delivery guarantees, see
anycable-coderskill. Action Cable provides at-most-once delivery which can lose chunks on reconnection.
<head>
<meta name="turbo-refresh-method" content="morph">
<meta name="turbo-refresh-scroll" content="preserve">
</head>
class Post < ApplicationRecord
after_update_commit -> { broadcast_refresh_to self }
end
<%= link_to "New Post", new_post_path, data: { turbo_frame: "modal" } %>
<turbo-frame id="modal"></turbo-frame>
<div id="posts"><%= render @posts %></div>
<% if @posts.next_page? %>
<turbo-frame id="pagination" src="<%= posts_path(page: @posts.next_page) %>" loading="lazy">
<p>Loading more...</p>
</turbo-frame>
<% end %>
| Anti-Pattern | Problem | Solution |
|---|---|---|
| Mismatched frame IDs | Silent failures | Validate IDs match |
| Missing status codes | Turbo ignores response | Use 422/303 correctly |
| Implicit locals in broadcasts | Runtime errors | Always pass request_id: nil |
Turbo.setDebug(true)
document.addEventListener("turbo:frame-missing", (e) => console.error("Frame not found:", e.detail.response))
When implementing Hotwire features, provide: