rails_claude_skills 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/.github/ISSUE_TEMPLATE/bug_report.yml +134 -0
- data/.github/ISSUE_TEMPLATE/config.yml +11 -0
- data/.github/ISSUE_TEMPLATE/feature_request.yml +129 -0
- data/.github/ISSUE_TEMPLATE/question.yml +90 -0
- data/.github/dependabot.yml +19 -0
- data/.github/workflows/ci.yml +77 -0
- data/.github/workflows/release.yml +66 -0
- data/.rubocop.yml +52 -0
- data/CHANGELOG.md +94 -0
- data/CLAUDE.md +332 -0
- data/CODE_OF_CONDUCT.md +134 -0
- data/CONTRIBUTING.md +580 -0
- data/LICENSE.txt +21 -0
- data/README.md +544 -0
- data/Rakefile +8 -0
- data/lib/generators/claude/agent/agent_generator.rb +71 -0
- data/lib/generators/claude/agent/templates/agent.md.tt +62 -0
- data/lib/generators/claude/command/command_generator.rb +50 -0
- data/lib/generators/claude/command/templates/command.md.tt +28 -0
- data/lib/generators/claude/commands_library/create-pr.md +27 -0
- data/lib/generators/claude/commands_library/dbchange.md +19 -0
- data/lib/generators/claude/commands_library/quality.md +20 -0
- data/lib/generators/claude/commands_library/stimulus.md +19 -0
- data/lib/generators/claude/commands_library/turbo-feature.md +17 -0
- data/lib/generators/claude/install/install_generator.rb +211 -0
- data/lib/generators/claude/install/templates/README.md.tt +59 -0
- data/lib/generators/claude/install/templates/USAGE +28 -0
- data/lib/generators/claude/install/templates/agents/api-dev.md.tt +46 -0
- data/lib/generators/claude/install/templates/agents/fullstack-dev.md.tt +48 -0
- data/lib/generators/claude/install/templates/agents/rails-developer.md.tt +40 -0
- data/lib/generators/claude/install/templates/settings.local.json.tt +13 -0
- data/lib/generators/claude/rule/rule_generator.rb +175 -0
- data/lib/generators/claude/rule/templates/rule.md.tt +7 -0
- data/lib/generators/claude/rules_library/code-style.md +37 -0
- data/lib/generators/claude/rules_library/database.md +47 -0
- data/lib/generators/claude/rules_library/hotwire.md +56 -0
- data/lib/generators/claude/rules_library/security.md +54 -0
- data/lib/generators/claude/rules_library/testing.md +47 -0
- data/lib/generators/claude/skill/skill_generator.rb +196 -0
- data/lib/generators/claude/skill/templates/SKILL.md.tt +27 -0
- data/lib/generators/claude/skills_library/create-task-files/SKILL.md +311 -0
- data/lib/generators/claude/skills_library/create-task-files/templates/bug.md +60 -0
- data/lib/generators/claude/skills_library/create-task-files/templates/epic.md +47 -0
- data/lib/generators/claude/skills_library/create-task-files/templates/issue.md +45 -0
- data/lib/generators/claude/skills_library/create-task-files/templates/user-story.md +57 -0
- data/lib/generators/claude/skills_library/minitest-testing/SKILL.md +398 -0
- data/lib/generators/claude/skills_library/minitest-testing/references/examples.md +889 -0
- data/lib/generators/claude/skills_library/plan-feature/SKILL.md +253 -0
- data/lib/generators/claude/skills_library/rails-api-controllers/SKILL.md +1041 -0
- data/lib/generators/claude/skills_library/rails-api-controllers/references/api-documentation.md +422 -0
- data/lib/generators/claude/skills_library/rails-api-controllers/references/serialization.md +456 -0
- data/lib/generators/claude/skills_library/rails-auth-with-devise/SKILL.md +191 -0
- data/lib/generators/claude/skills_library/rails-auth-with-devise/references/advanced.md +331 -0
- data/lib/generators/claude/skills_library/rails-auth-with-devise/references/api-auth.md +266 -0
- data/lib/generators/claude/skills_library/rails-auth-with-devise/references/omniauth.md +194 -0
- data/lib/generators/claude/skills_library/rails-authorization-cancancan/SKILL.md +603 -0
- data/lib/generators/claude/skills_library/rails-authorization-cancancan/references/api-authorization.md +543 -0
- data/lib/generators/claude/skills_library/rails-authorization-cancancan/references/complex-permissions.md +572 -0
- data/lib/generators/claude/skills_library/rails-authorization-cancancan/references/multi-tenancy.md +373 -0
- data/lib/generators/claude/skills_library/rails-controllers/SKILL.md +514 -0
- data/lib/generators/claude/skills_library/rails-debugging/SKILL.md +260 -0
- data/lib/generators/claude/skills_library/rails-deployment/SKILL.md +437 -0
- data/lib/generators/claude/skills_library/rails-deployment/references/examples.md +901 -0
- data/lib/generators/claude/skills_library/rails-hotwire/SKILL.md +367 -0
- data/lib/generators/claude/skills_library/rails-jobs/MISSION_CONTROL_SETUP.md +639 -0
- data/lib/generators/claude/skills_library/rails-jobs/SKILL.md +704 -0
- data/lib/generators/claude/skills_library/rails-mailers/SKILL.md +549 -0
- data/lib/generators/claude/skills_library/rails-models/SKILL.md +379 -0
- data/lib/generators/claude/skills_library/rails-pagination-kaminari/SKILL.md +622 -0
- data/lib/generators/claude/skills_library/rails-pagination-kaminari/references/api-pagination.md +523 -0
- data/lib/generators/claude/skills_library/rails-pagination-kaminari/references/custom-themes.md +498 -0
- data/lib/generators/claude/skills_library/rails-pagination-kaminari/references/performance.md +478 -0
- data/lib/generators/claude/skills_library/rails-views/SKILL.md +508 -0
- data/lib/generators/claude/skills_library/refine-requirements/SKILL.md +226 -0
- data/lib/generators/claude/skills_library/refine-requirements/references/examples.md +344 -0
- data/lib/generators/claude/skills_library/refine-requirements/references/reference.md +298 -0
- data/lib/generators/claude/skills_library/rspec-testing/SKILL.md +572 -0
- data/lib/generators/claude/skills_library/rspec-testing/references/better_specs_guide.md +273 -0
- data/lib/generators/claude/skills_library/rspec-testing/references/thoughtbot_patterns.md +407 -0
- data/lib/generators/claude/skills_library/tailwindcss/SKILL.md +371 -0
- data/lib/generators/claude/views/views_generator.rb +113 -0
- data/lib/rails_claude_skills/railtie.rb +16 -0
- data/lib/rails_claude_skills/version.rb +5 -0
- data/lib/rails_claude_skills.rb +27 -0
- data/sig/rails_claude_skills.rbs +4 -0
- metadata +199 -0
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: rails-hotwire
|
|
3
|
+
description: Hotwire (Turbo Drive, Turbo Frames, Turbo Streams, Stimulus)
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
rails_version: ">= 7.0"
|
|
6
|
+
tags:
|
|
7
|
+
- hotwire
|
|
8
|
+
- turbo
|
|
9
|
+
- stimulus
|
|
10
|
+
- frontend
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# Rails Hotwire
|
|
14
|
+
|
|
15
|
+
Hotwire is an alternative approach to building modern web applications without using much JavaScript. It consists of Turbo (Drive, Frames, Streams) and Stimulus.
|
|
16
|
+
|
|
17
|
+
## Quick Reference
|
|
18
|
+
|
|
19
|
+
| Component | Purpose |
|
|
20
|
+
|-----------|---------|
|
|
21
|
+
| **Turbo Drive** | Fast page navigation without full reload |
|
|
22
|
+
| **Turbo Frames** | Decompose pages into independent contexts |
|
|
23
|
+
| **Turbo Streams** | Deliver page changes as HTML over WebSocket or in response to form submissions |
|
|
24
|
+
| **Stimulus** | JavaScript sprinkles for enhanced interactivity |
|
|
25
|
+
|
|
26
|
+
## Turbo Drive
|
|
27
|
+
|
|
28
|
+
Turbo Drive automatically makes all link clicks and form submissions use AJAX, replacing the page body without full reload.
|
|
29
|
+
|
|
30
|
+
```erb
|
|
31
|
+
<%# Turbo Drive is enabled by default in Rails 7+ %>
|
|
32
|
+
<%# Links and forms automatically use Turbo Drive %>
|
|
33
|
+
|
|
34
|
+
<%= link_to "Posts", posts_path %>
|
|
35
|
+
<%= link_to "External Site", "https://example.com", data: { turbo: false } %>
|
|
36
|
+
|
|
37
|
+
<%# Opt-out for specific elements %>
|
|
38
|
+
<div data-turbo="false">
|
|
39
|
+
<%= link_to "Normal Link", some_path %>
|
|
40
|
+
</div>
|
|
41
|
+
|
|
42
|
+
<%# Programmatic navigation %>
|
|
43
|
+
<a href="/posts" data-turbo-action="advance">Posts</a>
|
|
44
|
+
<a href="/posts" data-turbo-action="replace">Posts (Replace History)</a>
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Turbo Frames
|
|
48
|
+
|
|
49
|
+
Turbo Frames allow you to scope navigation and form submissions to a specific part of the page.
|
|
50
|
+
|
|
51
|
+
### Basic Frame
|
|
52
|
+
|
|
53
|
+
```erb
|
|
54
|
+
<%# app/views/posts/index.html.erb %>
|
|
55
|
+
<%= turbo_frame_tag "posts_list" do %>
|
|
56
|
+
<%= render @posts %>
|
|
57
|
+
<%= link_to "New Post", new_post_path %>
|
|
58
|
+
<% end %>
|
|
59
|
+
|
|
60
|
+
<%# app/views/posts/new.html.erb %>
|
|
61
|
+
<%= turbo_frame_tag "posts_list" do %>
|
|
62
|
+
<h2>New Post</h2>
|
|
63
|
+
<%= form_with model: @post do |f| %>
|
|
64
|
+
<%= f.text_field :title %>
|
|
65
|
+
<%= f.submit %>
|
|
66
|
+
<% end %>
|
|
67
|
+
<% end %>
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Lazy Loading Frames
|
|
71
|
+
|
|
72
|
+
```erb
|
|
73
|
+
<%# Load content on demand %>
|
|
74
|
+
<%= turbo_frame_tag "post_#{post.id}", src: post_path(post), loading: :lazy do %>
|
|
75
|
+
<div class="loading">Loading post...</div>
|
|
76
|
+
<% end %>
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Targeting Frames
|
|
80
|
+
|
|
81
|
+
```erb
|
|
82
|
+
<%# Target a specific frame from outside %>
|
|
83
|
+
<%= link_to "Edit", edit_post_path(@post), data: { turbo_frame: "modal" } %>
|
|
84
|
+
<%= link_to "Break out of frame", posts_path, data: { turbo_frame: "_top" } %>
|
|
85
|
+
|
|
86
|
+
<%# Turbo Frame that breaks out by default %>
|
|
87
|
+
<%= turbo_frame_tag "navigation", target: "_top" do %>
|
|
88
|
+
<%= link_to "Home", root_path %>
|
|
89
|
+
<% end %>
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Turbo Streams
|
|
93
|
+
|
|
94
|
+
Turbo Streams let you update multiple parts of the page in response to actions.
|
|
95
|
+
|
|
96
|
+
### Stream Actions
|
|
97
|
+
|
|
98
|
+
```erb
|
|
99
|
+
<%# app/views/posts/create.turbo_stream.erb %>
|
|
100
|
+
|
|
101
|
+
<%# Append to a container %>
|
|
102
|
+
<%= turbo_stream.append "posts" do %>
|
|
103
|
+
<%= render @post %>
|
|
104
|
+
<% end %>
|
|
105
|
+
|
|
106
|
+
<%# Prepend to a container %>
|
|
107
|
+
<%= turbo_stream.prepend "posts" do %>
|
|
108
|
+
<%= render @post %>
|
|
109
|
+
<% end %>
|
|
110
|
+
|
|
111
|
+
<%# Replace an element %>
|
|
112
|
+
<%= turbo_stream.replace @post do %>
|
|
113
|
+
<%= render @post %>
|
|
114
|
+
<% end %>
|
|
115
|
+
|
|
116
|
+
<%# Update content of an element %>
|
|
117
|
+
<%= turbo_stream.update "post_#{@post.id}" do %>
|
|
118
|
+
<%= render @post %>
|
|
119
|
+
<% end %>
|
|
120
|
+
|
|
121
|
+
<%# Remove an element %>
|
|
122
|
+
<%= turbo_stream.remove "post_#{@post.id}" %>
|
|
123
|
+
|
|
124
|
+
<%# Insert before/after %>
|
|
125
|
+
<%= turbo_stream.before "post_#{@post.id}" do %>
|
|
126
|
+
<div class="notice">Updated!</div>
|
|
127
|
+
<% end %>
|
|
128
|
+
|
|
129
|
+
<%= turbo_stream.after "post_#{@post.id}" do %>
|
|
130
|
+
<div class="ad">Advertisement</div>
|
|
131
|
+
<% end %>
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Controller Setup
|
|
135
|
+
|
|
136
|
+
```ruby
|
|
137
|
+
class PostsController < ApplicationController
|
|
138
|
+
def create
|
|
139
|
+
@post = Post.new(post_params)
|
|
140
|
+
|
|
141
|
+
respond_to do |format|
|
|
142
|
+
if @post.save
|
|
143
|
+
format.turbo_stream
|
|
144
|
+
format.html { redirect_to @post }
|
|
145
|
+
else
|
|
146
|
+
format.turbo_stream { render :form_errors, status: :unprocessable_entity }
|
|
147
|
+
format.html { render :new, status: :unprocessable_entity }
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def update
|
|
153
|
+
respond_to do |format|
|
|
154
|
+
if @post.update(post_params)
|
|
155
|
+
format.turbo_stream
|
|
156
|
+
format.html { redirect_to @post }
|
|
157
|
+
else
|
|
158
|
+
format.turbo_stream { render :form_errors, status: :unprocessable_entity }
|
|
159
|
+
format.html { render :edit, status: :unprocessable_entity }
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def destroy
|
|
165
|
+
@post.destroy
|
|
166
|
+
|
|
167
|
+
respond_to do |format|
|
|
168
|
+
format.turbo_stream { render turbo_stream: turbo_stream.remove(@post) }
|
|
169
|
+
format.html { redirect_to posts_path }
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Broadcast Updates (Real-time)
|
|
176
|
+
|
|
177
|
+
```ruby
|
|
178
|
+
# app/models/post.rb
|
|
179
|
+
class Post < ApplicationRecord
|
|
180
|
+
broadcasts_to ->(post) { "posts" }, inserts_by: :prepend
|
|
181
|
+
|
|
182
|
+
# Or more control
|
|
183
|
+
after_create_commit -> { broadcast_prepend_to "posts" }
|
|
184
|
+
after_update_commit -> { broadcast_replace_to "posts" }
|
|
185
|
+
after_destroy_commit -> { broadcast_remove_to "posts" }
|
|
186
|
+
|
|
187
|
+
# Broadcast to specific users/channels
|
|
188
|
+
after_create_commit -> { broadcast_prepend_to [author, "posts"], target: "posts_list" }
|
|
189
|
+
end
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
```erb
|
|
193
|
+
<%# Subscribe to broadcasts %>
|
|
194
|
+
<%= turbo_stream_from "posts" %>
|
|
195
|
+
|
|
196
|
+
<div id="posts">
|
|
197
|
+
<%= render @posts %>
|
|
198
|
+
</div>
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
## Stimulus
|
|
202
|
+
|
|
203
|
+
Stimulus is a modest JavaScript framework for enhancing static or server-rendered HTML.
|
|
204
|
+
|
|
205
|
+
### Basic Controller
|
|
206
|
+
|
|
207
|
+
```javascript
|
|
208
|
+
// app/javascript/controllers/clipboard_controller.js
|
|
209
|
+
import { Controller } from "@hotwired/stimulus"
|
|
210
|
+
|
|
211
|
+
export default class extends Controller {
|
|
212
|
+
static targets = ["source"]
|
|
213
|
+
static values = {
|
|
214
|
+
message: String,
|
|
215
|
+
count: { type: Number, default: 0 }
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
copy() {
|
|
219
|
+
navigator.clipboard.writeText(this.sourceTarget.value)
|
|
220
|
+
this.countValue++
|
|
221
|
+
alert(`Copied! (${this.countValue} times)`)
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
```erb
|
|
227
|
+
<div data-controller="clipboard">
|
|
228
|
+
<input data-clipboard-target="source" type="text" value="Copy me!">
|
|
229
|
+
<button data-action="click->clipboard#copy">Copy to Clipboard</button>
|
|
230
|
+
</div>
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### Common Patterns
|
|
234
|
+
|
|
235
|
+
```javascript
|
|
236
|
+
// Toggle visibility
|
|
237
|
+
import { Controller } from "@hotwired/stimulus"
|
|
238
|
+
|
|
239
|
+
export default class extends Controller {
|
|
240
|
+
static targets = ["content"]
|
|
241
|
+
|
|
242
|
+
toggle() {
|
|
243
|
+
this.contentTarget.classList.toggle("hidden")
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
```erb
|
|
249
|
+
<div data-controller="toggle">
|
|
250
|
+
<button data-action="click->toggle#toggle">Toggle</button>
|
|
251
|
+
<div data-toggle-target="content" class="hidden">
|
|
252
|
+
Hidden content
|
|
253
|
+
</div>
|
|
254
|
+
</div>
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
```javascript
|
|
258
|
+
// Form autosave
|
|
259
|
+
import { Controller } from "@hotwired/stimulus"
|
|
260
|
+
|
|
261
|
+
export default class extends Controller {
|
|
262
|
+
static targets = ["form", "status"]
|
|
263
|
+
|
|
264
|
+
async save() {
|
|
265
|
+
this.statusTarget.textContent = "Saving..."
|
|
266
|
+
|
|
267
|
+
const formData = new FormData(this.formTarget)
|
|
268
|
+
const response = await fetch(this.formTarget.action, {
|
|
269
|
+
method: this.formTarget.method,
|
|
270
|
+
body: formData,
|
|
271
|
+
headers: {
|
|
272
|
+
"X-CSRF-Token": document.querySelector("[name='csrf-token']").content
|
|
273
|
+
}
|
|
274
|
+
})
|
|
275
|
+
|
|
276
|
+
if (response.ok) {
|
|
277
|
+
this.statusTarget.textContent = "Saved!"
|
|
278
|
+
} else {
|
|
279
|
+
this.statusTarget.textContent = "Error saving"
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
```erb
|
|
286
|
+
<div data-controller="autosave">
|
|
287
|
+
<%= form_with model: @post, data: { autosave_target: "form", action: "change->autosave#save" } do |f| %>
|
|
288
|
+
<%= f.text_area :content %>
|
|
289
|
+
<% end %>
|
|
290
|
+
<span data-autosave-target="status"></span>
|
|
291
|
+
</div>
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
## Best Practices
|
|
295
|
+
|
|
296
|
+
1. **Use Turbo Frames** for independent page sections
|
|
297
|
+
2. **Use Turbo Streams** for real-time updates and multi-part updates
|
|
298
|
+
3. **Keep Stimulus controllers small** and focused on one behavior
|
|
299
|
+
4. **Use Stimulus values** instead of reading from the DOM
|
|
300
|
+
5. **Graceful degradation** - ensure functionality works without JavaScript when possible
|
|
301
|
+
6. **Use broadcasts** for real-time features (chat, notifications, etc.)
|
|
302
|
+
7. **Test Turbo interactions** with system tests
|
|
303
|
+
8. **Use `data-turbo-confirm`** for destructive actions
|
|
304
|
+
9. **Optimize** by lazy-loading frames and using caching
|
|
305
|
+
|
|
306
|
+
## Common Patterns
|
|
307
|
+
|
|
308
|
+
### Modal with Turbo Frame
|
|
309
|
+
|
|
310
|
+
```erb
|
|
311
|
+
<%# Layout %>
|
|
312
|
+
<div id="modal" class="modal">
|
|
313
|
+
<%= turbo_frame_tag "modal_content" %>
|
|
314
|
+
</div>
|
|
315
|
+
|
|
316
|
+
<%# Link that opens modal %>
|
|
317
|
+
<%= link_to "New Post", new_post_path, data: { turbo_frame: "modal_content" } %>
|
|
318
|
+
|
|
319
|
+
<%# app/views/posts/new.html.erb %>
|
|
320
|
+
<%= turbo_frame_tag "modal_content" do %>
|
|
321
|
+
<h2>New Post</h2>
|
|
322
|
+
<%= render "form", post: @post %>
|
|
323
|
+
<% end %>
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
### Inline Editing
|
|
327
|
+
|
|
328
|
+
```erb
|
|
329
|
+
<%# Show mode %>
|
|
330
|
+
<%= turbo_frame_tag "post_#{@post.id}" do %>
|
|
331
|
+
<h2><%= @post.title %></h2>
|
|
332
|
+
<%= link_to "Edit", edit_post_path(@post) %>
|
|
333
|
+
<% end %>
|
|
334
|
+
|
|
335
|
+
<%# Edit mode %>
|
|
336
|
+
<%# app/views/posts/edit.html.erb %>
|
|
337
|
+
<%= turbo_frame_tag "post_#{@post.id}" do %>
|
|
338
|
+
<%= form_with model: @post do |f| %>
|
|
339
|
+
<%= f.text_field :title %>
|
|
340
|
+
<%= f.submit "Save" %>
|
|
341
|
+
<% end %>
|
|
342
|
+
<% end %>
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
### Pagination with Turbo Frames
|
|
346
|
+
|
|
347
|
+
```erb
|
|
348
|
+
<%= turbo_frame_tag "posts" do %>
|
|
349
|
+
<%= render @posts %>
|
|
350
|
+
<%= link_to "Load More", posts_path(page: @next_page) %>
|
|
351
|
+
<% end %>
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
## Troubleshooting
|
|
355
|
+
|
|
356
|
+
- **Frame not updating**: Check that frame IDs match
|
|
357
|
+
- **Form not working**: Ensure you're using `form_with` not `form_for`
|
|
358
|
+
- **Turbo Drive issues**: Use `data-turbo="false"` to opt-out
|
|
359
|
+
- **Broadcasts not working**: Check ActionCable is configured
|
|
360
|
+
- **Stimulus controller not connecting**: Check console for errors, verify data-controller name
|
|
361
|
+
|
|
362
|
+
## References
|
|
363
|
+
|
|
364
|
+
- [Turbo Handbook](https://turbo.hotwired.dev/handbook/introduction)
|
|
365
|
+
- [Stimulus Handbook](https://stimulus.hotwired.dev/handbook/introduction)
|
|
366
|
+
- [Hotwire Homepage](https://hotwired.dev/)
|
|
367
|
+
- [Turbo Rails Gem](https://github.com/hotwired/turbo-rails)
|