diff --git a/.ai/hotwire.blade.php b/.ai/hotwire.blade.php
new file mode 100644
index 0000000..91d70f2
--- /dev/null
+++ b/.ai/hotwire.blade.php
@@ -0,0 +1,229 @@
+## Hotwire/Turbo Core Principles
+- For standard application development, use Hotwire (Turbo + Stimulus)
+- Send HTML over the wire instead of JSON. Keep complexity on the server side.
+- Use Turbo Drive for smooth page transitions without full page reloads.
+- Decompose pages with Turbo Frames for independent sections that update separately.
+- Use Turbo Streams for real-time updates and dynamic content changes.
+- Leverage Stimulus for progressive JavaScript enhancement when Turbo isn't sufficient (if Stimulus is available)
+- Prefer server-side template rendering and state management over client-side frameworks.
+- Enable "morphing" for seamless page updates that preserve scroll position and focus.
+- Use data attributes for JavaScript hooks
+- For more complex JavaScript dependencies, use Importmap Laravel
+
+## Turbo Setup & Base Helpers
+@verbatim
+- Turbo automatically handles page navigation, form submissions, and CSRF protection
+- Enable morphing in your layout (preserves DOM state during page updates): ``
+- Configure scroll behavior in your layout: ``
+- Enable both morphing and scroll preservation with a single component: ``
+- Generate unique DOM IDs from models: use function `dom_id($model, 'optional_prefix')` or Blade directive `@domid($model, 'optional_prefix')`
+- Generate CSS classes from models: use function `dom_class($model, 'optional_prefix')` or Blade directive `@domclass($model, 'optional_prefix')`
+@endverbatim
+
+## Turbo Frames Best Practices
+- Use frames to decompose pages into independent sections that can update without full page reloads:
+@verbatim
+ ```blade
+
+ {{ $post->title }}
+ {{ $post->content }}
+ Edit
+
+ ```
+@endverbatim
+- Forms and links inside frames automatically target their containing frame (no configuration needed):
+@verbatim
+ ```blade
+
+
+
+ ```
+@endverbatim
+- Override default frame targeting with `data-turbo-frame` attribute:
+ - Use a frame's DOM ID to target a specific frame
+ - Use `_top` to break out of frames and navigate the full page:
+@verbatim
+ ```blade
+ View Full Post
+ ```
+@endverbatim
+
+## Turbo Streams for Dynamic Updates
+- Return Turbo Stream responses from controllers to update specific page elements without full page reload:
+@verbatim
+
+ public function store(Request $request)
+ {
+ $post = Post::create($request->validated());
+
+ if ($request->wantsTurboStream()) {
+ return turbo_stream([
+ turbo_stream()->append('posts', view('posts.partials.post', ['post' => $post])),
+ turbo_stream()->update('create_post', view('posts.partials.form', ['post' => new Post()])),
+ ]);
+ }
+
+ return back();
+ }
+
+@endverbatim
+- Available Turbo Stream actions for manipulating DOM elements:
+@verbatim
+
+ // Append content
+ turbo_stream()->append($comment, view('comments.partials.comment', [
+ 'comment' => $comment,
+ ]));
+
+ // Prepend content
+ turbo_stream()->prepend($comment, view('comments.partials.comment', [
+ 'comment' => $comment,
+ ]));
+
+ // Insert before
+ turbo_stream()->before($comment, view('comments.partials.comment', [
+ 'comment' => $comment,
+ ]));
+
+ // Insert after
+ turbo_stream()->after($comment, view('comments.partials.comment', [
+ 'comment' => $comment,
+ ]));
+
+ // Replace content (swaps the target element)
+ turbo_stream()->replace($comment, view('comments.partials.comment', [
+ 'comment' => $comment,
+ ]));
+
+ // Update content (keeps the target element and only updates its contents)
+ turbo_stream()->update($comment, view('comments.partials.comment', [
+ 'comment' => $comment,
+ ]));
+
+ // Removes content
+ turbo_stream()->remove($comment);
+
+@endverbatim
+- Broadcast Turbo Streams over WebSockets to push real-time updates to all connected users:
+@verbatim
+
+ // Add the trait to the model:
+ use HotwiredLaravel\TurboLaravel\Models\Broadcasts;
+
+ class Post extends Model
+ {
+ use Broadcasts;
+ }
+
+ // When you want to trigger the broadcasting from anywhere (including model events)...
+ $post->broadcastAppend()->to('posts');
+ $post->broadcastUpdate();
+ $post->broadcastRemove();
+
+@endverbatim
+
+## Form Handling & Validation
+- Use Laravel's resource route naming conventions for automatic form re-rendering, if the matching route exists:
+ - `*.store` action redirects to `*.create` route (shows form again with validation errors)
+ - `*.update` action redirects to `*.edit` route (shows form again with validation errors)
+ - `*.destroy` action redirects to `*.delete` route
+- Validation errors are automatically displayed when using this convention with Turbo
+
+## Performance & UX Enhancements
+- Use `data-turbo-permanent` to preserve specific elements during Turbo navigation (prevents re-rendering):
+@verbatim
+ ```blade
+
+
+
+ ```
+@endverbatim
+- Preloading is automatically enabled on all links. You may disable it for specific links with the `data-turbo-preload` attribute (if you need to):
+@verbatim
+ ```blade
+
+ {{ $post->title }}
+
+ ```
+@endverbatim
+
+## Testing Hotwire/Turbo
+@verbatim
+
+ public function test_creating_post_returns_turbo_stream()
+ {
+ $this->turbo()
+ ->post(route('posts.store'), ['title' => 'Test Post'])
+ ->assertTurboStream(fn (AssertableTurboStream $turboStreams) => (
+ $turboStreams->has(2)
+ && $turboStreams->hasTurboStream(fn ($turboStream) => (
+ $turboStream->where('target', 'flash_messages')
+ ->where('action', 'prepend')
+ ->see('Post was successfully created!')
+ ))
+ && $turboStreams->hasTurboStream(fn ($turboStream) => (
+ $turboStream->where('target', 'posts')
+ ->where('action', 'append')
+ ->see('Test Post')
+ ))
+ ));
+ }
+
+@endverbatim
+@verbatim
+
+ public function test_frame_request_returns_partial_content()
+ {
+ $this->fromTurboFrame(dom_id($post))
+ ->get(route('posts.update', $post))
+ ->assertSee('', false)
+ ->assertViewIs('posts.edit');
+ }
+
+@endverbatim
+@verbatim
+
+ use HotwiredLaravel\TurboLaravel\Facades\TurboStream;
+ use HotwiredLaravel\TurboLaravel\Broadcasting\PendingBroadcast;
+
+ public function test_post_creation_broadcasts_stream()
+ {
+ TurboStream::fake();
+
+ $post = Post::create(['title' => 'Test Post']);
+
+ TurboStream::assertBroadcasted(function (PendingBroadcast $broadcast) use ($post) {
+ return $broadcast->target === 'posts'
+ && $broadcast->action === 'append'
+ && $broadcast->partialView === 'posts.partials.post'
+ && $broadcast->partialData['post']->is($post)
+ && count($broadcast->channels) === 1
+ && $broadcast->channels[0]->name === sprintf('private-%s', $post->broadcastChannel());
+ });
+ }
+
+@endverbatim
+@verbatim
+
+ use HotwiredLaravel\TurboLaravel\Facades\TurboStream;
+ use HotwiredLaravel\TurboLaravel\Broadcasting\PendingBroadcast;
+
+ public function creating_comments_from_native_recedes()
+ {
+ $post = Post::factory()->create();
+
+ $this->assertCount(0, $post->comments);
+
+ $this->hotwireNative()->post(route('posts.comments.store', $post), [
+ 'content' => 'Hello World',
+ ])->assertRedirectRecede(['status' => __('Comment created.')]);
+
+ $this->assertCount(1, $post->refresh()->comments);
+ $this->assertEquals('Hello World', $post->comments->first()->content);
+ }
+
+@endverbatim
diff --git a/docs/helpers.md b/docs/helpers.md
index 937e543..afa1c50 100644
--- a/docs/helpers.md
+++ b/docs/helpers.md
@@ -11,7 +11,7 @@ Turbo Laravel has a set of Blade Directives, Components, helper functions, and r
## Blade Directives
-### The `@domid()` Blade Directive
+### The DOM ID Blade Directive
Since Turbo relies a lot on DOM IDs, the package offers a helper to generate unique DOM IDs based on your models. You may use the `@domid` Blade Directive in your Blade views like so:
@@ -33,7 +33,7 @@ Which will generate a `comments_post_123` DOM ID, assuming your Post model has a
## Blade Components
-### The `` Blade Component
+### The Turbo Frame Blade Component
You may also prefer using the `` Blade component that ships with the package. This way, you don't need to worry about using the `@domid()` helper for your Turbo Frame:
@@ -53,7 +53,7 @@ To the `:id` prop, you may pass a string, which will be used as-is as the DOM ID
Additionally, you may also pass along any prop that is supported by the Turbo Frame custom Element to the `` Blade component, like `target`, `src`, or `loading`. These are the listed attributes, but any other attribute will also be forwarded to the `` tag that will be rendered by the `` component. For a full list of what's possible to do with Turbo Frames, see the [documentation](https://turbo.hotwired.dev/handbook/frames).
-### The `` Blade Component
+### The Turbo Stream Blade Component
If you're rendering a Turbo Stream inside a your Blade files, you may use the `` helper:
@@ -65,7 +65,7 @@ If you're rendering a Turbo Stream inside a your Blade files, you may use the `<
Just like in the Turbo Frames' `:id` prop, the `:target` prop of the Turbo Stream component accepts a string, a model instance, or an array to resolve the DOM ID using the `dom_id()` function.
-### The `` Blade Component
+### The Refresh Method Blade Component
We can configure which update method Turbo should so to update the document:
@@ -86,7 +86,7 @@ The output would be:
```
-### The `` Blade Component
+### The Refresh Scroll Behavior Blade Component
You can also configure the scroll behavior on Turbo:
@@ -107,7 +107,7 @@ The output would be:
```
-### The `` Blade Component
+### The Refresh Behaviors Blade Component
You may configure both the refresh method and scroll behavior using the `` component in your main layout's `` tag or on specific pages to configure how Turbo should update the page. Here's an example:
@@ -122,7 +122,7 @@ This will render two HTML `` tags:
```
-### The `` Blade Component
+### The Page Cache Exemption Blade Component
This component may be added to any page you don't want Turbo to keep a cache in the page cache. Example:
@@ -136,7 +136,7 @@ It will render the HTML `` tag:
```
-### The `` Blade Component
+### The Page Preview Exemption Blade Component
This component may be added to any page you don't want Turbo to show as a preview on regular navigation visits. No-preview pages will only be used in restoration visits (when you use the browser's back or forward buttons, or when when moving backward in the navigation stack). Example:
@@ -150,7 +150,7 @@ It will render the HTML `` tag:
```
-### The `` Blade Component
+### The Page Reload Blade Component
This component may be added to any page you want Turbo to reload. This will break out of Turbo Frame navigations. May be used at a login screen, for instance. Example:
@@ -168,7 +168,7 @@ It will render the HTML `` tag:
The package ships with a set of helper functions. These functions are all namespaced under `HotwiredLaravel\\TurboLaravel\\` but we also add them globally for convenience, so you may use them directly without the `use` statements (this is useful in contexts like Blade views, for instance).
-### The `dom_id()`
+### The DOM ID Helper Function
The mentioned namespaced `dom_id()` helper function may also be used from anywhere in your application, like so:
@@ -182,7 +182,7 @@ When a new instance of a model is passed to any of these DOM ID helpers, since i
These helpers strip out the model's FQCN (see [config/turbo-laravel.php](https://github.com/hotwired-laravel/turbo-laravel/blob/main/config/turbo-laravel.php) if you use an unconventional location for your models).
-### The `dom_class()`
+### The DOM CSS Class Helper Function
The `dom_class()` helper function may be used from anywhere in your application, like so:
@@ -202,7 +202,7 @@ dom_class($comment, 'reactions_list');
This will generate a DOM class of `reactions_list_comment`.
-### The `turbo_stream()`
+### The Turbo Stream Helper Function
You may generate Turbo Streams using the `Response::turboStream()` macro, but you may also do so using the `turbo_stream()` helper function:
@@ -214,7 +214,7 @@ turbo_stream()->append($comment);
Both the `Response::turboStream()` and the `turbo_stream()` function work the same way. The `turbo_stream()` function may be easier to use.
-### The `turbo_stream_view()`
+### The Turbo Stream View Helper Function
You may combo Turbo Streams using the `turbo_stream([])` function passing an array, but you may prefer to create a separate Blade view with all the Turbo Streams, this way you may also use template extensions and everything else Blade offers:
@@ -228,13 +228,13 @@ return turbo_stream_view('comments.turbo.created', [
## Request & Response Macros
-### The `request()->wantsTurboStream()` macro
+### Detect If Request Accepts Turbo Streams
The `request()->wantsTurboStream()` macro added to the request class will check if the request accepts Turbo Stream and return `true` or `false` accordingly.
Turbo will add a `Accept: text/vnd.turbo-stream.html, ...` header to the requests. That's how we can detect if the request came from a client using Turbo.
-### The `request()->wasFromTurboFrame()` macro
+### Detect If Request Was Made From Turbo Frame
The `request()->wasFromTurboFrame()` macro added to the request class will check if the request was made from a Turbo Frame. When used with no parameters, it returns `true` if the request has a `Turbo-Frame` header, no matter which specific Turbo Frame.
@@ -246,16 +246,16 @@ if (request()->wasFromTurboFrame(dom_id($post, 'create_comment'))) {
}
```
-### The `request()->wasFromHotwireNative()` macro
+### Detect If Request Was Made From Hotwire Native Client
The `request()->wasFromHotwireNative()` macro added to the request class will check if the request came from a Hotwire Native client and returns `true` or `false` accordingly.
Hotwire Native clients are encouraged to override the `User-Agent` header in the WebViews to mention the words `Hotwire Native` on them. This is what this macro uses to detect if it came from a Hotwire Native client.
-### The `response()->turboStream()` macro
+### Turbo Stream Response Macro
The `response()->turboStream()` macro works similarly to the `turbo_stream()` function above. It was only added to the response for convenience.
-### The `response()->turboStreamView()` macro
+### The Turbo Stream View Response Macro
The `response()->turboStreamView()` macro works similarly to the `turbo_stream_view()` function above. It was only added to the response for convenience.
diff --git a/docs/turbo-frames.md b/docs/turbo-frames.md
index 78921fa..7f53cae 100644
--- a/docs/turbo-frames.md
+++ b/docs/turbo-frames.md
@@ -39,7 +39,7 @@ Any other attribute passed to the Blade Component will get forwarded to the unde
This will work for any other attribute you want to forward to the underlying component.
-## The `request()->wasFromTurboFrame()` Macro
+## Detecting Turbo Frames Requests
You may want to detect if a request came from a Turbo Frame in the backend. You may use the `wasFromTurboFrame()` method for that:
diff --git a/src/Commands/PublishBoostGuidelineCommand.php b/src/Commands/PublishBoostGuidelineCommand.php
new file mode 100644
index 0000000..67dfb49
--- /dev/null
+++ b/src/Commands/PublishBoostGuidelineCommand.php
@@ -0,0 +1,23 @@
+info('Boost guideline was published!');
+ }
+}
diff --git a/src/TurboServiceProvider.php b/src/TurboServiceProvider.php
index 85790ce..dd6e86e 100644
--- a/src/TurboServiceProvider.php
+++ b/src/TurboServiceProvider.php
@@ -5,6 +5,7 @@
use HotwiredLaravel\TurboLaravel\Broadcasters\Broadcaster;
use HotwiredLaravel\TurboLaravel\Broadcasters\LaravelBroadcaster;
use HotwiredLaravel\TurboLaravel\Broadcasting\Limiter;
+use HotwiredLaravel\TurboLaravel\Commands\PublishBoostGuidelineCommand;
use HotwiredLaravel\TurboLaravel\Commands\TurboInstallCommand;
use HotwiredLaravel\TurboLaravel\Facades\Turbo as TurboFacade;
use HotwiredLaravel\TurboLaravel\Http\Middleware\TurboMiddleware;
@@ -76,6 +77,7 @@ private function configurePublications(): void
$this->commands([
TurboInstallCommand::class,
+ PublishBoostGuidelineCommand::class,
]);
}