turbo_ready 0.0.3 → 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -1,5 +1,8 @@
1
1
  <p align="center">
2
- <img height="200" src="https://ik.imagekit.io/hopsoft/turbo-ready-logo_jYFJI1jgT.png?ik-sdk-version=javascript-1.4.3&updatedAt=1661471047153" />
2
+ <picture>
3
+ <source media="(prefers-color-scheme: dark)" srcset="https://ik.imagekit.io/hopsoft/turbo-ready-logo-light_df4jcvbDL.webp?ik-sdk-version=javascript-1.4.3&updatedAt=1661615678275">
4
+ <img height="200" src="https://ik.imagekit.io/hopsoft/turbo-ready-logo-dark_VN4hA2ctc.webp?ik-sdk-version=javascript-1.4.3&updatedAt=1661615678278" />
5
+ </picture>
3
6
  <h3 align="center">
4
7
  Turbo Stream's Swiss Army Knife
5
8
  </h3>
@@ -7,44 +10,76 @@
7
10
  Welcome to TurboReady 👋
8
11
  </h1>
9
12
  <p align="center">
10
- <a href="http://blog.codinghorror.com/the-best-code-is-no-code-at-all/" target="_blank">
11
- <img alt="Lines of Code" src="https://img.shields.io/badge/lines_of_code-275-brightgreen.svg?style=flat" />
13
+ <a href="http://blog.codinghorror.com/the-best-code-is-no-code-at-all/">
14
+ <img alt="Lines of Code" src="https://img.shields.io/badge/loc-143-47d299.svg" />
12
15
  </a>
13
- <a href="https://github.com/testdouble/standard" target="_blank">
14
- <img alt="Ruby Code Style" src="https://img.shields.io/badge/Ruby_Code_Style-standard-brightgreen.svg" />
16
+ <a href="https://codeclimate.com/github/hopsoft/turbo_ready/maintainability">
17
+ <img src="https://api.codeclimate.com/v1/badges/a69b6f73abc3ccd49261/maintainability" />
15
18
  </a>
16
- <a href="https://github.com/sheerun/prettier-standard" target="_blank">
17
- <img alt="JavaScript Code Style" src="https://img.shields.io/badge/JavaScript_Code_Style-prettier_standard-ff69b4.svg" />
19
+ <a href="https://rubygems.org/gems/turbo_ready">
20
+ <img alt="GEM" src="https://img.shields.io/gem/v/turbo_ready?color=168AFE&include_prereleases&logo=ruby&logoColor=FE1616">
21
+ </a>
22
+ <a href="https://rubygems.org/gems/turbo_ready">
23
+ <img alt="Gem" src="https://img.shields.io/gem/dt/turbo_ready?color=168AFE&logo=ruby&logoColor=FE1616">
24
+ </a>
25
+ <a href="https://github.com/testdouble/standard">
26
+ <img alt="Ruby Style" src="https://img.shields.io/badge/style-standard-168AFE?logo=ruby&logoColor=FE1616" />
27
+ </a>
28
+ <a href="https://www.npmjs.com/package/turbo_ready">
29
+ <img alt="NPM" src="https://img.shields.io/npm/v/turbo_ready?color=168AFE&logo=npm">
30
+ </a>
31
+ <a href="https://www.npmjs.com/package/turbo_ready">
32
+ <img alt="npm" src="https://img.shields.io/npm/dm/turbo_ready?color=168AFE&logo=npm">
33
+ </a>
34
+ <a href="https://bundlephobia.com/package/turbo_ready@">
35
+ <img alt="npm bundle size" src="https://img.shields.io/bundlephobia/minzip/turbo_ready?label=bundle%20size&logo=npm&color=47d299">
36
+ </a>
37
+ <a href="https://github.com/sheerun/prettier-standard">
38
+ <img alt="JavaScript Style" src="https://img.shields.io/badge/style-prettier--standard-168AFE?logo=javascript&logoColor=f4e137" />
39
+ </a>
40
+ <a href="https://github.com/hopsoft/turbo_ready/actions/workflows/tests.yml">
41
+ <img alt="Tests" src="https://github.com/hopsoft/turbo_ready/actions/workflows/tests.yml/badge.svg" />
42
+ </a>
43
+ <a href="https://twitter.com/hopsoft">
44
+ <img alt="Twitter Follow" src="https://img.shields.io/twitter/follow/hopsoft?logo=twitter&style=social">
18
45
  </a>
19
46
  </p>
20
47
  </p>
21
48
 
22
- TurboReady extends [Turbo Streams](https://turbo.hotwired.dev/reference/streams) to give you full control of the
23
- browser's [Document Object Model (DOM).](https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model)
24
-
25
- **Thats right!**
26
- You can `invoke` any DOM method on any DOM object *(including 3rd party libs)* using Turbo Streams.
49
+ **TurboReady extends [Turbo Streams](https://turbo.hotwired.dev/reference/streams) to give you full control of the
50
+ browser's [Document Object Model (DOM).](https://developer.mozilla.org/en-US/docs/Web/API/Document_Object_Model)**
27
51
 
28
52
  ```ruby
29
- turbo_stream.invoke "console.log", "Hello World!"
53
+ turbo_stream.invoke "console.log", args: ["Hello World!"]
30
54
  ```
31
55
 
56
+ **Thats right!**
57
+ You can `invoke` any DOM method on the client with Turbo Streams.
58
+
32
59
  <!-- Tocer[start]: Auto-generated, don't remove. -->
33
60
 
34
61
  ## Table of Contents
35
62
 
36
63
  - [Why TurboReady?](#why-turboready)
37
- - [Discord Community](#discord-community)
38
64
  - [Sponsors](#sponsors)
39
65
  - [Dependencies](#dependencies)
40
66
  - [Installation](#installation)
41
67
  - [Setup](#setup)
42
68
  - [Usage](#usage)
43
- - [Endless Possibilities](#endless-possibilities)
44
- - [Advanced Usage](#advanced-usage)
69
+ - [Method Chaining](#method-chaining)
70
+ - [Dispatching Events](#dispatching-events)
71
+ - [Syntax Styles](#syntax-styles)
45
72
  - [Extending Behavior](#extending-behavior)
46
- - [Public API](#public-api)
73
+ - [Implementation Details](#implementation-details)
74
+ - [Broadcasting](#broadcasting)
75
+ - [Background Job Queues](#background-job-queues)
76
+ - [FAQ](#faq)
47
77
  - [A Word of Caution](#a-word-of-caution)
78
+ - [Community](#community)
79
+ - [Discord](#discord)
80
+ - [Discussions](#discussions)
81
+ - [Twitter](#twitter)
82
+ - [TODOs](#todos)
48
83
  - [Releasing](#releasing)
49
84
  - [License](#license)
50
85
 
@@ -52,21 +87,17 @@ turbo_stream.invoke "console.log", "Hello World!"
52
87
 
53
88
  ## Why TurboReady?
54
89
 
55
- Turbo Streams [intentionally restricts](https://turbo.hotwired.dev/handbook/streams#but-what-about-running-javascript)
90
+ Turbo Streams [intentionally restricts](https://turbo.hotwired.dev/handbook/streams#but-what-about-running-javascript%3F)
56
91
  official actions to CRUD related activity.
57
- The [official actions](https://turbo.hotwired.dev/reference/streams#the-seven-actions) work well for a
58
- considerable number of use cases and you should push Streams as far as possible before reaching for TurboReady.
59
-
60
- If you discover that CRUD isn't enough, TurboReady covers pretty much everything else.
61
-
62
- ## Community
92
+ These [official actions](https://turbo.hotwired.dev/reference/streams#the-seven-actions) work well for a considerable number of use cases.
93
+ *Try pushing Turbo Streams as far as possible before reaching for TurboReady.*
63
94
 
64
- Please join nearly 2000 of us on [Discord](https://discord.gg/stimulus-reflex) for support getting started,
65
- as well as active discussions around Rails, Hotwire, Stimulus, Turbo (Drive, Frames, Streams), TurboReady, CableReady, StimulusReflex, ViewComponent, Phlex, and more.
95
+ If you find that CRUD isn't enough, TurboReady is there to handle pretty much everything else.
66
96
 
67
- ![](https://img.shields.io/discord/629472241427415060)
97
+ > ⚠️ TurboReady is intended for Rails apps that use Hotwire but not [CableReady](https://github.com/stimulusreflex/cable_ready).
98
+ This is because CableReady already provides a rich set of powerful [DOM operations](https://cableready.stimulusreflex.com/reference/operations).
68
99
 
69
- Stop by #newcomers and introduce yourselves!
100
+ > 📘 **NOTE:** Efforts are underway to bring [CableReady's DOM operations to Turbo Streams](https://github.com/marcoroth/turbo_power).
70
101
 
71
102
  ## Sponsors
72
103
 
@@ -87,79 +118,84 @@ Stop by #newcomers and introduce yourselves!
87
118
 
88
119
  ## Installation
89
120
 
121
+ Be sure to install the same version for each libary.
122
+
90
123
  ```sh
91
124
  bundle add "turbo_ready --version VERSION"
92
125
  yarn add "turbo_ready@VERSION --exact"
93
126
  ```
94
127
 
95
- **IMPORTANT:** Be sure to use the same version for each libary.
96
-
97
128
  ## Setup
98
129
 
99
- 1. Create a Rails intializer and patch Turbo.
130
+ Import and intialize TurboReady in your application.
100
131
 
101
- ```ruby
102
- # config/initializers/turbo_ready.rb
103
- TurboReady.patch! # Adds TurboReady stream actions to Turbo
104
- ```
105
- 2. Import and intialize TurboReady in your JavaScript application.
132
+ ```diff
133
+ # Gemfile
134
+ +gem "turbo_ready", "~> 0.0.6"
135
+ ```
106
136
 
107
- ```js
108
- // app/javascript/application.js
109
- import '@hotwired/turbo-rails'
110
- import TurboReady from 'turbo_ready'
137
+ ```diff
138
+ # package.json
139
+ "dependencies": {
140
+ + "@hotwired/turbo-rails": ">=7.2.0-beta.2",
141
+ + "turbo_ready": "^0.0.6"
142
+ ```
111
143
 
112
- TurboReady.initialize(Turbo.StreamActions) // Adds TurboReady stream actions to Turbo
113
- ```
144
+ ```diff
145
+ # app/javascript/application.js
146
+ import '@hotwired/turbo-rails'
147
+ +import TurboReady from 'turbo_ready'
148
+
149
+ +TurboReady.initialize(Turbo.StreamActions) // Adds TurboReady stream actions to Turbo
150
+ ```
114
151
 
115
152
  ## Usage
116
153
 
117
- Manipulate the DOM from anywhere you use [official Turbo Streams](https://turbo.hotwired.dev/handbook/streams#integration-with-server-side-frameworks).
118
- Namely, [**M**odels](https://github.com/hotwired/turbo-rails/blob/main/app/models/concerns/turbo/broadcastable.rb),
119
- [**V**iews](https://github.com/hotwired/turbo-rails/blob/main/app/models/concerns/turbo/broadcastable.rb),
120
- and [**C**ontrollers](https://github.com/hotwired/turbo-rails/blob/main/app/models/concerns/turbo/broadcastable.rb).
154
+ Manipulate the DOM from anywhere you use official [Turbo Streams](https://turbo.hotwired.dev/handbook/streams#integration-with-server-side-frameworks).
155
+ The possibilities are endless.
156
+ [Learn more about the DOM at MDN.](https://developer.mozilla.org/en-US/docs/Web/API.)
121
157
 
122
- You can **chain invocations.** ❤️
158
+ ```ruby
159
+ turbo_stream.invoke "console.log", args: ["Hello World!"]
160
+ ```
161
+
162
+ ### Method Chaining
163
+
164
+ You can use [dot notation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Property_accessors#dot_notation)
165
+ or [selectors](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelectorAll) and even combine them!
123
166
 
124
167
  ```ruby
125
168
  turbo_stream
126
- .invoke("document.body.insertAdjacentHTML", "afterbegin", "<h1>Hello World!</h1>") # dot notation
127
- .invoke("setAttribute", "data-turbo-ready", true, selector: ".button") # selector
128
- .invoke("classList.add", "turbo-ready", selector: "a") # dot notation + selector
129
- .flush # flush must be called when chaining invocations
169
+ .invoke("document.body.insertAdjacentHTML", args: ["afterbegin", "<h1>Hello World!</h1>"]) # dot notation
170
+ .invoke("setAttribute", args: ["data-turbo-ready", true], selector: ".button") # selector
171
+ .invoke("classList.add", args: ["turbo-ready"], selector: "a") # dot notation + selector
172
+ .flush # call flush when chaining invocations
130
173
  ```
131
174
 
132
- You can use [dot notation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Property_accessors#dot_notation)
133
- or [selectors](https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelectorAll)... and can even combine them!** 🤯
175
+ ### Dispatching Events
134
176
 
135
- Can I dispatch events? **You bet!** ⚡️
177
+ It's possible to fire events on `window`, `document`, and element(s).
136
178
 
137
179
  ```ruby
138
180
  turbo_stream
139
- .invoke("dispatchEvent", "turbo-ready:demo") // fires on window
140
- .invoke("dispatchEvent", "turbo-ready:demo", selector: "#my-element") // fires on matching element(s)
141
- .invoke("dispatchEvent", {bubbles: true, detail: {...}}) // set event options
181
+ .invoke("dispatchEvent", args: ["turbo-ready:demo"]) # fires on window
182
+ .invoke("document.dispatchEvent", args: ["turbo-ready:demo"]) # fires on document
183
+ .invoke("dispatchEvent", args: ["turbo-ready:demo"], selector: "#my-element") # fires on matching element(s)
184
+ .invoke("dispatchEvent", args: ["turbo-ready:demo", {bubbles: true, detail: {...}}]) # set event options
142
185
  .flush
143
186
  ```
144
187
 
145
- ## Endless Possibilities
146
-
147
- **What else can I do?**
148
- MDN has your back... [learn about the DOM and web APIs here.](https://developer.mozilla.org/en-US/docs/Web/API.)
188
+ ### Syntax Styles
149
189
 
150
- ## Advanced Usage
151
-
152
- You can use symbols and [snake case](https://en.wikipedia.org/wiki/Snake_case) when invoking DOM functionality.
153
- It'll implicitly convert to [camel case](https://en.wikipedia.org/wiki/Camel_case). 💎
190
+ You can use [`snake_case`](https://en.wikipedia.org/wiki/Snake_case) when invoking DOM functionality.
191
+ It will implicitly convert to [`camelCase`](https://en.wikipedia.org/wiki/Camel_case).
154
192
 
155
193
  ```ruby
156
- turbo_stream
157
- .invoke(:animate, [{opacity: 0}, {opacity: 1}], 2000)
158
- .invoke(:dispatch_event, {detail: {converts_to_camel_case: true}})
159
- .flush
194
+ turbo_stream.invoke :dispatch_event,
195
+ args: ["turbo-ready:demo", {detail: {converts_to_camel_case: true}}]
160
196
  ```
161
197
 
162
- Need to opt out of camelize? No problem... just disable it.
198
+ Need to opt-out? No problem... just disable it.
163
199
 
164
200
  ```ruby
165
201
  turbo_stream.invoke :contrived_demo, camelize: false
@@ -167,10 +203,10 @@ turbo_stream.invoke :contrived_demo, camelize: false
167
203
 
168
204
  ### Extending Behavior
169
205
 
170
- Want to extend things with custom functionality? **Let's do it.** 🔌
206
+ If you add new capabilities to the browser, you can control them from the server.
171
207
 
172
208
  ```js
173
- // JavaScript
209
+ // JavaScript on the client
174
210
  import morphdom from 'morphdom'
175
211
 
176
212
  window.MyNamespace = {
@@ -181,51 +217,180 @@ window.MyNamespace = {
181
217
  ```
182
218
 
183
219
  ```ruby
184
- # Ruby
185
- turbo_stream
186
- .invoke "MyNamespace.morph", "#demo", "<div id='demo'><p>You've changed...</p></div>", {childrenOnly: true}
220
+ # Ruby on the server
221
+ turbo_stream.invoke "MyNamespace.morph",
222
+ args: [
223
+ "#demo",
224
+ "<div id='demo'><p>You've changed...</p></div>",
225
+ {children_only: true}
226
+ ]
187
227
  ```
188
228
 
189
- ## Public API
229
+ ### Implementation Details
190
230
 
191
- There's only one method to consider, `invoke` defined in the
192
- [tag builder](https://github.com/hopsoft/turbo_ready/blob/main/lib/turbo_ready/tag_builder.rb).
231
+ There's basically one method to learn... `invoke`
193
232
 
194
233
  ```ruby
195
234
  # Ruby
196
235
  turbo_stream
197
- .invoke(method, *args, selector: nil, camelize: true, id: nil)
198
- # | | | | |
199
- # | | | | |- Identifies this invocation (optional)
200
- # | | | |
201
- # | | | |- Should we camelize the JavaScript stuff? (optional)
202
- # | | | (allows us to write snake_case Ruby)
203
- # | | |
204
- # | | |- An CSS selector for the element(s) to target (optional)
205
- # | |
206
- # | |- The arguments to pass to the JavaScript method being invoked (optional)
236
+ .invoke(method, args: [], selector: nil, camelize: true, id: nil)
237
+ # | | | | |
238
+ # | | | | |- Identifies this invocation (optional)
239
+ # | | | |
240
+ # | | | |- Should we camelize the JavaScript stuff? (optional)
241
+ # | | | (allows us to write snake_case in Ruby)
242
+ # | | |
243
+ # | | |- A CSS selector for the element(s) to target (optional)
244
+ # | |
245
+ # | |- The arguments to pass to the JavaScript method (optional)
207
246
  # |
208
247
  # |- The JavaScript method to invoke (can use dot notation)
209
248
  ```
210
249
 
211
- **NOTE:** The JavaScript method will be invoked on all matching elements when a `selector` is passed.
250
+ > 📘 **NOTE:** The method will be invoked on all matching elements if a `selector` is present.
251
+
252
+ The following Ruby code,
253
+
254
+ ```ruby
255
+ turbo_stream.invoke "console.log", args: ["Hello World!"], id: "1"
256
+ ```
257
+
258
+ emits this HTML markup.
259
+
260
+ ```html
261
+ <turbo-stream action="invoke" target="DOM">
262
+ <template>{"id":"1","receiver":"console","method":"log","args":["Hello World!"]}</template>
263
+ </turbo-stream>
264
+ ```
265
+
266
+ When this element enters the DOM,
267
+ Turbo Streams automatically executes `invoke` on the client with the template's JSON payload and then removes the element from the DOM.
268
+
269
+ ### Broadcasting
270
+
271
+ You can also broadcast DOM invocations to subscribed users.
272
+
273
+ 1. First, setup the stream subscription.
274
+
275
+ ```erb
276
+ <!-- app/views/posts/show.html.erb -->
277
+ <%= turbo_stream_from @post %>
278
+ <!-- |
279
+ |- *streamables - model(s), string(s), etc...
280
+ -->
281
+ ```
282
+
283
+ 2. Then, broadcast to the subscription.
284
+
285
+ ```ruby
286
+ # app/models/post.rb
287
+ class Post < ApplicationRecord
288
+ after_save do
289
+ # emit a message in the browser conosle for anyone subscribed to this post
290
+ broadcast_invoke "console.log", args: ["Post was saved! #{to_gid.to_s}"]
291
+
292
+ # broadcast with a background job
293
+ broadcast_invoke_later "console.log", args: ["Post was saved! #{to_gid.to_s}"]
294
+ end
295
+ end
296
+ ```
297
+
298
+ ```ruby
299
+ # app/controllers/posts_controller.rb
300
+ class PostsController < ApplicationController
301
+ def create
302
+ @post = Post.find params[:id]
303
+
304
+ if @post.update post_params
305
+ # emit a message in the browser conosle for anyone subscribed to this post
306
+ @post.broadcast_invoke "console.log", args: ["Post was saved! #{to_gid.to_s}"]
307
+
308
+ # broadcast with a background job
309
+ @post.broadcast_invoke_later "console.log", args: ["Post was saved! #{to_gid.to_s}"]
310
+
311
+ # you can also broadcast directly from the channel
312
+ Turbo::StreamsChannel.broadcast_invoke_to @post, "console.log",
313
+ args: ["Post was saved! #{@post.to_gid.to_s}"]
314
+
315
+ # broadcast with a background job
316
+ Turbo::StreamsChannel.broadcast_invoke_later_to @post, "console.log",
317
+ args: ["Post was saved! #{@post.to_gid.to_s}"]
318
+ end
319
+ end
320
+ end
321
+ ```
322
+
323
+ > 📘 **NOTE:** [Method Chaining](#method-chaining) is not currently supported when broadcasting.
324
+
325
+ #### Background Job Queues
326
+
327
+ You may want to change the queue name for Turbo Stream background jobs in order to isolate, prioritize, and scale the workers independently.
328
+
329
+ ```ruby
330
+ # config/initializers/turbo_streams.rb
331
+ Turbo::Streams::BroadcastJob.queue_name = :turbo_streams
332
+ TurboReady::BroadcastInvokeJob.queue_name = :turbo_streams
333
+ ```
334
+
335
+ ## FAQ
336
+
337
+ - Isn't this just RJS?
338
+
339
+ > No. But, perhaps it could be considered RJS's "modern" spirtual successor. 🤷‍♂️
340
+ > Though it embraces JavaScript instead of trying to avoid it.
341
+
342
+ - Does it use `eval`?
343
+
344
+ > **No.** TurboReady can only invoke existing functions on the client.
345
+ > It's not a carte blanche invitation to emit free-form JavaScript to be evaluated on the client.
212
346
 
213
347
  ## A Word of Caution
214
348
 
215
- Manually orchestrating DOM activity gets tedious fast.
216
- **⚠️ Don't abuse this superpower!**
349
+ **Don't abuse this superpower!**
217
350
 
218
351
  > With great power comes great responsibility. *-Uncle Ben*
219
352
 
353
+ Manually orchestrating DOM activity is tedious.
354
+ *Don't overdo it... or you may find that you've created spaghetti reminiscent of the jQuery days.*
355
+
220
356
  This library is an extremely sharp tool. 🔪
221
- Consider it a low-level building block that can be used to craft additional libraries with
222
- great [DX](https://en.wikipedia.org/wiki/User_experience#Developer_experience)
357
+ Consider it a low-level building block that can be used to craft additional libraries
223
358
  like [CableReady](https://github.com/stimulusreflex/cable_ready)
224
359
  and [StimulusReflex](https://github.com/stimulusreflex/stimulus_reflex).
225
360
 
226
- Restrict your direct application usage to DOM manipulation that falls outside the purview of
227
- [Turbo's official actions](https://turbo.hotwired.dev/reference/streams#the-seven-actions)...
228
- *and for Pete's sake, don't overdo it and find yourself maintaining spaghetti code reminiscent of the jQuery days.*
361
+ ## Community
362
+
363
+ ### Discord
364
+
365
+ Please join nearly 2000 of us on [Discord](https://discord.gg/stimulus-reflex) for support getting started,
366
+ as well as active discussions around Rails, Hotwire, Stimulus, Turbo (Drive, Frames, Streams), TurboReady, CableReady, StimulusReflex, ViewComponent, Phlex, and more.
367
+
368
+ <a href="https://discord.gg/stimulus-reflex" target="_blank">
369
+ <img alt="Discord" src="https://img.shields.io/discord/629472241427415060?color=168AFE&logo=discord&logoColor=FFF">
370
+ </a>
371
+
372
+ Be sure to introduce yourselves in the #newcomers channel!
373
+
374
+ ### Discussions
375
+
376
+ Feel free to add to the conversation here on [GitHub Discussions](https://github.com/hopsoft/turbo_ready/discussions).
377
+
378
+ <a href="https://github.com/hopsoft/turbo_ready/discussions" target="_blank">
379
+ <img alt="GitHub Discussions" src="https://img.shields.io/github/discussions/hopsoft/turbo_ready?color=168AFE&logo=github">
380
+ </a>
381
+
382
+ ### Twitter
383
+
384
+ Connect with the core team on Twitter.
385
+
386
+ <a href="https://twitter.com/hopsoft" target="_blank">
387
+ <img alt="Twitter Follow" src="https://img.shields.io/twitter/follow/hopsoft?logo=twitter&style=social">
388
+ </a>
389
+
390
+ ## TODOs
391
+
392
+ - [ ] Add system tests [(review turbo-rails for guidance)](https://github.com/hotwired/turbo-rails/blob/main/test/system/broadcasts_test.rb)
393
+ - [ ] Look into adding method chaining for broadcasts
229
394
 
230
395
  ## Releasing
231
396