# Search Results

## Components Page components
Components | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/components

## Page components

Components can be routed to directly as full pages using `Route::livewire()`. This is one of Livewire's most powerful features, allowing you to build entire pages without traditional controllers.

```php
Route::livewire('/posts/create', 'pages::post.create');
```

When a user visits `/posts/create`, Livewire will render the `pages::post.create` component inside your application's layout file.

Page components work just like regular components, but they're rendered as full pages with access to:
- Custom layouts
- Page titles
- Route parameters and model binding
- Named slots for layouts

For complete information about page components, including layouts, titles, and advanced routing, see the [Pages documentation](/docs/4.x/pages).

---

## Components Modal > Modal
Components Modal | livewire/flux@2.x | Source: https://fluxui.dev/components/modal#livewire/flux

    Modal

Display content in a layer above the main page. Ideal for confirmations, alerts, and forms.

<flux:modal.trigger name="edit-profile">    <flux:button>Edit profile</flux:button></flux:modal.trigger><flux:modal name="edit-profile" class="md:w-96">

            <flux:heading size="lg">Update profile</flux:heading>            <flux:text class="mt-2">Make changes to your personal details.</flux:text>
        <flux:input label="Name" placeholder="Your name" />        <flux:input label="Date of birth" type="date" />
            <flux:spacer />            <flux:button type="submit" variant="primary">Save changes</flux:button>

</flux:modal>

---

## Wire Submit See also
Wire Submit | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/wire-submit

## See also

- **[Forms](/docs/4.x/forms)** — Handle form submissions with Livewire
- **[Actions](/docs/4.x/actions)** — Process form data in actions
- **[Validation](/docs/4.x/validation)** — Validate forms before submission

---

## Session Properties Setting a custom key
Session Properties | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/session-properties

## Setting a custom key

When using `[#Session]`, Livewire will store the property value in the session using a dynamically generated key that consists of the component name combined with the property name.

This ensures that properties across component instances will use the same session value. It also ensures properties of the same name from different components won't conflict.

If you want full control over what session key Livewire uses for a given property, you can pass the `key:` parameter:

```php
<?php

use Livewire\Attributes\Session;
use Livewire\Component;

class ShowPosts extends Component
{
    #[Session(key: 'search')] // [tl! highlight]
    public $search;

    // ...
}
```

When Livewire stores and retrieves the value of the `$search` property, it will use the given key: "search".

Additionally, if you want to generate the key dynamically from other properties in your component, you can do so using the following curly brace notation:

```php
<?php

use Livewire\Attributes\Session;
use Livewire\Component;
use App\Models\Author;

class ShowPosts extends Component
{
    public Author $author;

    #[Session(key: 'search-{author.id}')] // [tl! highlight]
    public $search;

    // ...
}
```

In the above example, if the `$author` model's id is "4", the session key will become: `search-4`

---

## Validation See also
Validation | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/validation

## See also

- **[Forms](/docs/4.x/forms)** — Validate form inputs with real-time feedback
- **[Properties](/docs/4.x/properties)** — Validate property values before persisting
- **[Validate Attribute](/docs/4.x/attribute-validate)** — Use #[Validate] for property validation
- **[Actions](/docs/4.x/actions)** — Validate data in action methods

---

## Tia
Tia | pestphp/pest@4.x | Source: https://github.com/pestphp/docs/blob/4.x/tia.md

---
title: Test Impact Analysis (TIA)
description: Test Impact Analysis is a great way to drastically reduce the time it takes to run your test suite by re-running only the tests affected by your latest changes.
---

# Test Impact Analysis (TIA)

Test Impact Analysis is a great way to drastically reduce the time it takes to run your test suite by re-running only the tests affected by your latest changes. The first time you run with `--tia`, Pest records a graph of which tests depend on which files. Every run after that, Pest looks at what you changed, runs only the tests that touched those files, and replays cached results for everything else.

A typical Laravel suite that takes 15 seconds replays in under a second. Edits to a single Blade template re-run a handful of feature tests. Comment-only edits, formatter passes, and README touches re-run nothing at all.

To get started, just add the `--tia` flag to any Pest invocation:

```bash
./vendor/bin/pest --parallel --tia
```

The first run is the **baseline** — Pest enables a coverage driver (PCOV or Xdebug) and records the dependency graph as your tests execute. Expect a small overhead on this run only.

Every subsequent run is a **replay**. Pest compares your working tree against the baseline and re-runs only the tests affected by your changes:

```
Tests:    774 passed (2658 assertions, 7 affected, 767 replayed)
Duration: 0.74s
```

`affected` is the set of tests Pest actually executed. `replayed` is the set whose results were served from cache.

## How Pest Decides What To Run

For each file you've changed, Pest looks for tests that depend on it:

- **PHP source files** — your `app/` classes, controllers, models, helpers — are tracked through the coverage driver. A change to `app/Models/User.php` re-runs only the tests that touched `User`.
- **Migrations** are intersected with the tables each test queried during the baseline. A column rename in `create_users_table.php` re-runs only the tests that queried the `users` table.
- **Inertia pages** under `resources/js/Pages` re-run only the tests that server-side-rendered them.
- **Shared JS components** under `resources/js/Components`, `Layouts`, and friends re-run only the tests whose pages import them — Pest walks Vite's module graph to figure this out.
- **Blade templates** re-run only the tests that rendered them, including renders triggered by browser tests.
- **Anything else** — config files, route files, `.env`, files outside the recorded graph — falls through to a broader pattern. Editing `config/app.php` re-runs every test, because Pest can't statically prove which tests depend on it.

## Cosmetic Edits Don't Run Anything

Pest normalises file content before comparing, so cosmetic changes don't trigger any tests. PHP files have whitespace, line comments, and docblocks stripped before hashing. Blade strips `{{-- … --}}` comments. JS, TS, Vue, and Svelte files have line and block comments removed too.

The result: a comment-only edit, a Prettier reformat, a Pint pass, or a README tweak produces an identical hash, and the file never enters the changed set. Zero tests run.

## Modes

Pest supports a few flags alongside `--tia`:

| Flag | Behaviour |
|---|---|
| `--tia` | Replay if a baseline graph exists, otherwise record. |
| `--tia --fresh` | Discard any existing graph and re-record from scratch. Use this after large refactors or when the graph feels stale. |
| `--tia --refetch` | Force a CI baseline fetch even within the 24-hour cooldown after a previous failed fetch. |
| `--tia --filtered` | Narrow PHPUnit to only the affected test files rather than loading all tests and replaying cached results for unaffected ones. |

## Sharing The Baseline From CI

Recording the baseline locally takes minutes on large suites. Instead, you can have CI record it once per merge to `main`, and every developer downloads the result.

When Pest detects no local graph (or the local graph is out of date), it uses GitHub's CLI to download the latest successful run of a `tia-baseline.yml` workflow's `pest-tia-baseline` artifact. Pest validates the fetched graph against your project state — if it matches, it's adopted. Otherwise it's discarded and a local rebuild proceeds.

Here's a starter workflow you can drop into `.github/workflows/tia-baseline.yml`:

```yaml
name: TIA Baseline
on:
  push: { branches: [main] }
  schedule: [{ cron: '0 3 * * *' }]
  workflow_dispatch:
jobs:
  baseline:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with: { fetch-depth: 0 }
      - uses: shivammathur/setup-php@v2
        with: { php-version: '8.4', coverage: xdebug }
      - run: composer install --no-interaction --prefer-dist
      - run: ./vendor/bin/pest --parallel --tia --coverage
      - name: Stage baseline for upload
        shell: bash
        run: |
          mkdir -p .pest-tia-baseline
          cp -R "$HOME/.pest/tia"/*/. .pest-tia-baseline/
      - uses: actions/upload-artifact@v4
        with:
          name: pest-tia-baseline
          path: .pest-tia-baseline/
          retention-days: 30
```

After CI runs, every developer who runs `pest --tia` for the first time on the repo will download this baseline and start replaying immediately, paying no record cost.

## Storage

Pest stores its state at `~/.pest/tia/<project-key>/`, where the project key is derived from your normalised git remote URL — so `git@github.com:foo/bar.git` and `https://github.com/foo/bar` produce the same key. A non-git project falls back to a hash of the project's absolute path.

Sharing state per remote URL means multiple worktrees of the same repository share one cache, while unrelated projects on the same machine stay isolated.

## Configuration

You can configure TIA behaviour in `tests/Pest.php` via `pest()->tia()`:

```php
pest()->tia()
    ->always()   // run TIA on every invocation, no --tia flag needed
    ->locally(); // restrict always() to local environments only
```

**`always()`** activates TIA for every `pest` run without requiring the `--tia` flag. Pair it with **`locally()`** to restrict that behaviour to local machines — on CI (detected via the `--ci` flag or the `CI` environment variable) TIA is skipped automatically. An explicit `--tia` on the command line always takes effect regardless.

**`filtered()`** enables filtered mode, equivalent to `--tia --filtered`. In this mode Pest narrows PHPUnit to only the affected test files rather than loading the full suite and replaying cached results for unaffected tests:

```php
pest()->tia()->filtered();
```

## Custom Watch Patterns

If your project has a directory layout that doesn't match the framework defaults, you can register custom watch patterns in `tests/Pest.php`:

```php
pest()->tia()->watch([
    'config/billing/**/*.php' => 'tests/Feature/Billing',
    'public/build/**/*'       => 'tests/Browser',
]);
```

Each glob maps to a single test directory. Whenever a matching file changes, every test under that directory is invalidated. Duplicate glob keys overwrite the built-in defaults, so you can redirect a pattern to a narrower directory.

---

Now that you've learned how to use Test Impact Analysis to speed up your test suite, let's discuss how to integrate Pest with your continuous integration workflow: [Continuous Integration](/docs/continuous-integration)

---

## Validation Form objects
Validation | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/validation

## Form objects

As more properties and validation rules are added to a Livewire component, it can begin to feel too crowded. To alleviate this pain and also provide a helpful abstraction for code reuse, you can use Livewire's *Form Objects* to store your properties and validation rules.

Below is the same `CreatePost` example, but now the properties and rules have been extracted to a dedicated form object named `PostForm`:

```php
<?php

namespace App\Livewire\Forms;

use Livewire\Attributes\Validate;
use Livewire\Form;

class PostForm extends Form
{
    #[Validate('required|min:3')]
	public $title = '';

    #[Validate('required|min:3')]
    public $content = '';
}
```

The `PostForm` above can now be defined as a property on the `CreatePost` component:

```php
<?php

namespace App\Livewire;

use App\Livewire\Forms\PostForm;
use Livewire\Component;
use App\Models\Post;

class CreatePost extends Component
{
    public PostForm $form;

    public function save()
    {
		Post::create(
    		$this->form->all()
    	);

		return redirect()->to('/posts');
    }

    // ...
}
```

As you can see, instead of listing out each property individually, we can retrieve all the property values using the `->all()` method on the form object.

Also, when referencing the property names in the template, you must prepend `form.` to each instance:

```blade
<form wire:submit="save">
	<input type="text" wire:model="form.title">
    <div>@error('form.title') {{ $message }} @enderror</div>

	<textarea wire:model="form.content"></textarea>
    <div>@error('form.content') {{ $message }} @enderror</div>

	<button type="submit">Save</button>
</form>
```

When using form objects, `#[Validate]` attribute validation will be run every time a property is updated. However, if you disable this behavior by specifying `onUpdate: false` on the attribute, you can manually run a form object's validation using `$this->form->validate()`:

```php
public function save()
{
    Post::create(
        $this->form->validate()
    );

    return redirect()->to('/posts');
}
```

Form objects are a useful abstraction for most larger datasets and a variety of additional features that make them even more powerful. For more information, check out the comprehensive [form object documentation](/docs/4.x/forms#extracting-a-form-object).

---

## Session Properties Basic usage
Session Properties | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/session-properties

## Basic usage

Here's a `show-posts` component that allows users to filter visible posts by a string stored in a `$search` property:

```php
<?php // resources/views/components/⚡show-posts.blade.php

use Livewire\Attributes\Computed;
use Livewire\Attributes\Session;
use Livewire\Component;
use App\Models\Post;

new class extends Component {
    #[Session] // [tl! highlight]
    public $search;

    #[Computed]
    public function posts()
    {
        return $this->search === ''
            ? Post::all()
            : Post::where('title', 'like', '%'.$this->search.'%');
    }
};
```

Because the `#[Session]` attribute has been added to the `$search` property, after a user enters a search value, they can refresh the page and the search value will be persisted. Every time `$search` is updated, its new value will be stored in the user's session and used across page loads.

> [!warning] Performance implications
> Because Laravel sessions are loaded into memory during every request, you can slow down the performance of your entire application for a given user by storing too much in a user's session.

---

## Undocumented Features Todo High Priority
Undocumented Features Todo | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/undocumented-features-todo

## High Priority

1. **`wire:intersect` directive**
   - Full API with all modifiers (`.once`, `.half`, `.full`, `.threshold.X`, `.margin.Xpx`)
   - Usage patterns and examples
   - Comparison with Alpine's `x-intersect`
   - Suggested location: New docs page or section in wire-* directives

2. **`data-loading` attribute**
   - Automatic addition to elements that trigger requests
   - Styling examples and use cases
   - Suggested location: Section in wire-loading.md or actions.md

3. **`wire:preserve-scroll` modifier**
   - How it works and when to use it
   - Examples of preventing layout jumps
   - Suggested location: Actions.md or new wire-preserve-scroll.md

4. **`$errors` magic property in JavaScript**
   - Full API (`.has()`, `.first()`, `.all()`, etc.)
   - Integration with Alpine
   - Suggested location: Forms.md or validation.md

5. **`$island()` magic action**
   - Calling island renders from templates
   - Passing modes and options
   - Suggested location: Islands.md (add a section)

6. **`$wire.$intercept()` magic**
   - Full API and callback structure
   - Use cases and examples
   - Suggested location: JavaScript.md (interceptors section)

7. **JavaScript modules in view-based components**
   - How `<script>` tags work without `@script` wrapper
   - Automatic `$wire` binding as `this`
   - Module caching and performance benefits
   - Suggested location: Components.md or JavaScript.md

8. **CSP-safe mode restrictions**
   - Complete list of what expressions are not allowed
   - Migration guide for existing apps
   - Workarounds and alternatives
   - Suggested location: New security.md section or dedicated csp.md page

---

## Lazy Full-page lazy loading
Lazy | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/lazy

## Full-page lazy loading

You can lazy load or defer full-page Livewire components using route methods.

### Lazy loading full pages

Use `->lazy()` to load the component when it enters the viewport:

```php
Route::livewire('/dashboard', 'pages::dashboard')->lazy();
```

### Deferring full pages

Use `->defer()` to load the component immediately after the page loads:

```php
Route::livewire('/dashboard', 'pages::dashboard')->defer();
```

### Disabling lazy/defer loading

If a component is lazy or deferred by default (via the `#[Lazy]` or `#[Defer]` attribute), you can opt-out using `enabled: false`:

```php
Route::livewire('/dashboard', 'pages::dashboard')->lazy(enabled: false);
Route::livewire('/dashboard', 'pages::dashboard')->defer(enabled: false);
```

---

## Session Interacting With the Session Retrieving Data The Global Session Helper
Session | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/session.md

#### The Global Session Helper

You may also use the global `session` PHP function to retrieve and store data in the session. When the `session` helper is called with a single, string argument, it will return the value of that session key. When the helper is called with an array of key / value pairs, those values will be stored in the session:

```php
Route::get('/home', function () {
    // Retrieve a piece of data from the session...
    $value = session('key');

    // Specifying a default value...
    $value = session('key', 'default');

    // Store a piece of data in the session...
    session(['key' => 'value']);
});
```

> [!NOTE]
> There is little practical difference between using the session via an HTTP request instance versus using the global `session` helper. Both methods are [testable](/docs/{{version}}/testing) via the `assertSessionHas` method which is available in all of your test cases.

---

## Redirecting Redirecting to full-page components
Redirecting | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/redirecting

## Redirecting to full-page components

Because Livewire uses Laravel's built-in redirection feature, you can use all of the redirection methods available to you in a typical Laravel application.

For example, if you are using a Livewire component as a full-page component for a route like so:

```php
Route::livewire('/posts', 'pages::show-posts');
```

You can redirect to it simply by using the route path:

```php
public function save()
{
    // ...

    $this->redirect('/posts');
}
```

---

## Testing See also
Testing | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/testing

## See also

- **[Actions](/docs/4.x/actions)** — Test component actions and interactions
- **[Forms](/docs/4.x/forms)** — Test form submissions and validation
- **[Events](/docs/4.x/events)** — Test event dispatching and listening
- **[Components](/docs/4.x/components)** — Create testable component structure

---

## Attribute Title Only for full-page components
Attribute Title | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/attribute-title

## Only for full-page components

> [!info] Full-page components only
> The `#[Title]` attribute only works for full-page components accessed via routes. Regular components rendered within other views don't use titles—they inherit the parent page's title.

---

## Session Properties
Session Properties | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/session-properties

Livewire makes it easy to persist property values across page refreshes/changes using the `#[Session]` attribute.

By adding `#[Session]` to a property in your component, Livewire will store that property's value in the session every time it changes. This way, when a page is refreshed, Livewire will fetch the latest value from the session and use it in your component.

The `#[Session]` attribute is analogous to the [`#[Url]`](/docs/4.x/url) attribute. They are both useful in similar scenarios. The primary difference being `#[Session]` persists values without modifying the URL's query string, which is sometimes desired; sometimes not.

---

## Understanding Nesting Performance implications
Understanding Nesting | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/understanding-nesting

## Performance implications

Livewire's independent component architecture can have both positive and negative implications for your application.

An advantage of this architecture is it allows you to isolate expensive portions of your application. For example, you can quarantine a slow database query to its own independent component, and its performance overhead won't impact the rest of the page.

However, the biggest drawback of this approach is that because components are entirely separate, inter-component communication/dependencies becomes more difficult.

For example, if you had a property passed down from the above parent `Posts` component to the nested `ShowPost` component, it wouldn't be "reactive". Because each component is independent, if a request to the parent component changed the value of the property being passed into `ShowPost`, it wouldn't update inside `ShowPost`.

Livewire has overcome a number of these hurdles and provides dedicated APIs for these scenarios like: [Reactive properties](/docs/4.x/nesting#reactive-props), [Modelable components](/docs/4.x/nesting#binding-to-child-data-using-wiremodel), and [the `$parent` object](/docs/4.x/nesting#directly-accessing-the-parent-from-the-child).

Armed with this knowledge of how nested Livewire components operate, you will be able to make more informed decisions about when and how to nest components within your application.

---

## Attribute Validate Learn more
Attribute Validate | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/attribute-validate

## Learn more

For comprehensive documentation on validation, including form objects, custom rules, and testing, see the [Validation documentation](/docs/4.x/validation).

---

## Forms See also
Forms | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/forms

## See also

- **[Validation](/docs/4.x/validation)** — Validate form inputs with real-time feedback
- **[wire:model](/docs/4.x/wire-model)** — Bind form inputs to component properties
- **[File Uploads](/docs/4.x/uploads)** — Handle file uploads in forms
- **[Actions](/docs/4.x/actions)** — Process form submissions with actions
- **[Loading States](/docs/4.x/loading-states)** — Show loading indicators during form submission

---

## Forms Submitting a form
Forms | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/forms

## Submitting a form

Let's start by looking at a very simple form in a `post.create` component. This form will have two simple text inputs and a submit button, as well as some code on the backend to manage the form's state and submission:

```php
<?php // resources/views/components/post/⚡create.blade.php

use Livewire\Component;
use App\Models\Post;

new class extends Component {
    public $title = '';

    public $content = '';

    public function save()
    {
        Post::create(
            $this->only(['title', 'content'])
        );

        session()->flash('status', 'Post successfully updated.');

        return $this->redirect('/posts');
    }
};
?>

<form wire:submit="save">
    <input type="text" wire:model="title">

    <input type="text" wire:model="content">

    <button type="submit">Save</button>
</form>
```

As you can see, we are "binding" the public `$title` and `$content` properties in the form above using `wire:model`. This is one of the most commonly used and powerful features of Livewire.

In addition to binding `$title` and `$content`, we are using `wire:submit` to capture the `submit` event when the "Save" button is clicked and invoking the `save()` action. This action will persist the form input to the database.

After the new post is created in the database, we redirect the user to the posts page and show them a "flash" message that the new post was created.

### Adding validation

To avoid storing incomplete or dangerous user input, most forms need some sort of input validation.

Livewire makes validating your forms as simple as adding `#[Validate]` attributes above the properties you want to be validated.

Once a property has a `#[Validate]` attribute attached to it, the validation rule will be applied to the property's value any time it's updated server-side.

Let's add some basic validation rules to the `$title` and `$content` properties in our `post.create` component:

```php
<?php // resources/views/components/post/⚡create.blade.php

use Livewire\Attributes\Validate; // [tl! highlight]
use Livewire\Component;
use App\Models\Post;

new class extends Component {
    #[Validate('required')] // [tl! highlight]
    public $title = '';

    #[Validate('required')] // [tl! highlight]
    public $content = '';

    public function save()
    {
        $this->validate(); // [tl! highlight]

        Post::create(
            $this->only(['title', 'content'])
        );

        return $this->redirect('/posts');
    }
};
```

We'll also modify our Blade template to show any validation errors on the page.

```blade
<form wire:submit="save">
    <input type="text" wire:model="title">
    <div>
        @error('title') <span class="error">{{ $message }}</span> @enderror <!-- [tl! highlight] -->
    </div>

    <input type="text" wire:model="content">
    <div>
        @error('content') <span class="error">{{ $message }}</span> @enderror <!-- [tl! highlight] -->
    </div>

    <button type="submit">Save</button>
</form>
```

Now, if the user tries to submit the form without filling in any of the fields, they will see validation messages telling them which fields are required before saving the post.

Livewire has a lot more validation features to offer. For more information, visit our [dedicated documentation page on Validation](/docs/4.x/validation).

### Extracting a form object

If you are working with a large form and prefer to extract all of its properties, validation logic, etc., into a separate class, Livewire offers form objects.

Form objects allow you to re-use form logic across components and provide a nice way to keep your component class cleaner by grouping all form-related code into a separate class.

You can either create a form class by hand or use the convenient artisan command:

```shell
php artisan livewire:form PostForm
```

The above command will create a file called `app/Livewire/Forms/PostForm.php`.

Let's rewrite the `post.create` component to use a `PostForm` class:

```php
<?php

namespace App\Livewire\Forms;

use Livewire\Attributes\Validate;
use Livewire\Form;

class PostForm extends Form
{
    #[Validate('required|min:5')]
    public $title = '';

    #[Validate('required|min:5')]
    public $content = '';
}
```

```php
<?php // resources/views/components/post/⚡create.blade.php

use App\Livewire\Forms\PostForm;
use Livewire\Component;
use App\Models\Post;

new class extends Component {
    public PostForm $form; // [tl! highlight]

    public function save()
    {
        $this->validate();

        Post::create(
            $this->form->only(['title', 'content']) // [tl! highlight]
        );

        return $this->redirect('/posts');
    }
};
```

```blade
<form wire:submit="save">
    <input type="text" wire:model="form.title">
    <div>
        @error('form.title') <span class="error">{{ $message }}</span> @enderror
    </div>

    <input type="text" wire:model="form.content">
    <div>
        @error('form.content') <span class="error">{{ $message }}</span> @enderror
    </div>

    <button type="submit">Save</button>
</form>
```

If you'd like, you can also extract the post creation logic into the form object like so:

```php
<?php

namespace App\Livewire\Forms;

use Livewire\Attributes\Validate;
use App\Models\Post;
use Livewire\Form;

class PostForm extends Form
{
    #[Validate('required|min:5')]
    public $title = '';

    #[Validate('required|min:5')]
    public $content = '';

    public function store() // [tl! highlight:5]
    {
        $this->validate();

        Post::create($this->only(['title', 'content']));
    }
}
```

Now you can call `$this->form->store()` from the component:

```php
<?php // resources/views/components/post/⚡create.blade.php

use App\Livewire\Forms\PostForm;
use Livewire\Component;

new class extends Component {
    public PostForm $form;

    public function save()
    {
        $this->form->store(); // [tl! highlight]

        return $this->redirect('/posts');
    }

    // ...
};
```

If you want to use this form object for both a create and update form, you can easily adapt it to handle both use cases.

Here's what it would look like to use this same form object for a `post.edit` component and fill it with initial data:

```php
<?php // resources/views/components/post/⚡edit.blade.php

use App\Livewire\Forms\PostForm;
use Livewire\Component;
use App\Models\Post;

new class extends Component {
    public PostForm $form;

    public function mount(Post $post)
    {
        $this->form->setPost($post);
    }

    public function save()
    {
        $this->form->update();

        return $this->redirect('/posts');
    }
};
```

```php
<?php

namespace App\Livewire\Forms;

use Livewire\Attributes\Validate;
use Livewire\Form;
use App\Models\Post;

class PostForm extends Form
{
    public ?Post $post;

    #[Validate('required|min:5')]
    public $title = '';

    #[Validate('required|min:5')]
    public $content = '';

    public function setPost(Post $post)
    {
        $this->post = $post;

        $this->title = $post->title;

        $this->content = $post->content;
    }

    public function store()
    {
        $this->validate();

        Post::create($this->only(['title', 'content']));
    }

    public function update()
    {
        $this->validate();

        $this->post->update(
            $this->only(['title', 'content'])
        );
    }
}
```

As you can see, we've added a `setPost()` method to the `PostForm` object to optionally allow for filling the form with existing data as well as storing the post on the form object for later use. We've also added an `update()` method for updating the existing post.

Form objects are not required when working with Livewire, but they do offer a nice abstraction for keeping your components free of repetitive boilerplate.

### Resetting form fields

If you are using a form object, you may want to reset the form after it has been submitted. This can be done by calling the `reset()` method:

```php
<?php

namespace App\Livewire\Forms;

use Livewire\Attributes\Validate;
use App\Models\Post;
use Livewire\Form;

class PostForm extends Form
{
    #[Validate('required|min:5')]
    public $title = '';

    #[Validate('required|min:5')]
    public $content = '';

    // ...

    public function store()
    {
        $this->validate();

        Post::create($this->only(['title', 'content']));

        $this->reset(); // [tl! highlight]
    }
}
```

You can also reset specific properties by passing the property names into the `reset()` method:

```php
$this->reset('title');

// Or multiple at once...

$this->reset(['title', 'content']);
```

### Pulling form fields

Alternatively, you can use the `pull()` method to both retrieve a form's properties and reset them in one operation.

```php
<?php

namespace App\Livewire\Forms;

use Livewire\Attributes\Validate;
use App\Models\Post;
use Livewire\Form;

class PostForm extends Form
{
    #[Validate('required|min:5')]
    public $title = '';

    #[Validate('required|min:5')]
    public $content = '';

    // ...

    public function store()
    {
        $this->validate();

        Post::create(
            $this->pull() // [tl! highlight]
        );
    }
}
```

You can also pull specific properties by passing the property names into the `pull()` method:

```php
// Return a value before resetting...
$this->pull('title');

 // Return a key-value array of properties before resetting...
$this->pull(['title', 'content']);
```

### Using Rule objects

If you have more sophisticated validation scenarios where Laravel's `Rule` objects are necessary, you can alternatively define a `rules()` method to declare your validation rules like so:

```php
<?php

namespace App\Livewire\Forms;

use Illuminate\Validation\Rule;
use App\Models\Post;
use Livewire\Form;

class PostForm extends Form
{
    public ?Post $post;

    public $title = '';

    public $content = '';

    protected function rules()
    {
        return [
            'title' => [
                'required',
                Rule::unique('posts')->ignore($this->post), // [tl! highlight]
            ],
            'content' => 'required|min:5',
        ];
    }

    // ...

    public function update()
    {
        $this->validate();

        $this->post->update($this->only(['title', 'content']));

        $this->reset();
    }
}
```

When using a `rules()` method instead of `#[Validate]`, Livewire will only run the validation rules when you call `$this->validate()`, rather than every time a property is updated.

If you are using real-time validation or any other scenario where you'd like Livewire to validate specific fields after every request, you can use `#[Validate]` without any provided rules like so:

```php
<?php

namespace App\Livewire\Forms;

use Livewire\Attributes\Validate;
use Illuminate\Validation\Rule;
use App\Models\Post;
use Livewire\Form;

class PostForm extends Form
{
    public ?Post $post;

    #[Validate] // [tl! highlight]
    public $title = '';

    public $content = '';

    protected function rules()
    {
        return [
            'title' => [
                'required',
                Rule::unique('posts')->ignore($this->post),
            ],
            'content' => 'required|min:5',
        ];
    }

    // ...

    public function update()
    {
        $this->validate();

        $this->post->update($this->only(['title', 'content']));

        $this->reset();
    }
}
```

Now if the `$title` property is updated before the form is submitted—like when using [`wire:model.live.blur`](/docs/4.x/wire-model#updating-on-blur-event)—the validation for `$title` will be run.

### Showing a loading indicator

By default, Livewire will automatically disable submit buttons and mark inputs as `readonly` while a form is being submitted, preventing the user from submitting the form again while the first submission is being handled.

However, it can be difficult for users to detect this "loading" state without extra affordances in your application's UI.

Here's an example of adding a small loading spinner to the "Save" button via `wire:loading` so that a user understands that the form is being submitted:

```blade
<button type="submit">
    Save

    <div wire:loading>
         <!-- SVG loading spinner -->
    </span>
</button>
```

[Learn more about loading states →](/docs/4.x/loading-states)

_[Truncated]_


Output:
Wall time: 4.0225 seconds
Output:
[{"type":"text","text":"# Search Results\n\n## Components Page components\nComponents | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/components\n\n## Page components\n\nComponents can be routed to directly as full pages using `Route::livewire()`. This is one of Livewire's most powerful features, allowing you to build entire pages without traditional controllers.\n\n```php\nRoute::livewire('/posts/create', 'pages::post.create');\n```\n\nWhen a user visits `/posts/create`, Livewire will render the `pages::post.create` component inside your application's layout file.\n\nPage components work just like regular components, but they're rendered as full pages with access to:\n- Custom layouts\n- Page titles\n- Route parameters and model binding\n- Named slots for layouts\n\nFor complete information about page components, including layouts, titles, and advanced routing, see the [Pages documentation](/docs/4.x/pages).\n\n---\n\n## Components Modal > Modal\nComponents Modal | livewire/flux@2.x | Source: https://fluxui.dev/components/modal#livewire/flux\n\n

\n    Modal\n

Display content in a layer above the main page. Ideal for confirmations, alerts, and forms.

<flux:modal.trigger name=\"edit-profile\">    <flux:button>Edit profile</flux:button></flux:modal.trigger><flux:modal name=\"edit-profile\" class=\"md:w-96\">

            <flux:heading size=\"lg\">Update profile</flux:heading>            <flux:text class=\"mt-2\">Make changes to your personal details.</flux:text>
        <flux:input label=\"Name\" placeholder=\"Your name\" />        <flux:input label=\"Date of birth\" type=\"date\" />
            <flux:spacer />            <flux:button type=\"submit\" variant=\"primary\">Save changes</flux:button>

</flux:modal>

\n\n---\n\n## Wire Submit See also\nWire Submit | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/wire-submit\n\n## See also\n\n- **[Forms](/docs/4.x/forms)** — Handle form submissions with Livewire\n- **[Actions](/docs/4.x/actions)** — Process form data in actions\n- **[Validation](/docs/4.x/validation)** — Validate forms before submission\n\n---\n\n## Session Properties Setting a custom key\nSession Properties | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/session-properties\n\n## Setting a custom key\n\nWhen using `[#Session]`, Livewire will store the property value in the session using a dynamically generated key that consists of the component name combined with the property name.\n\nThis ensures that properties across component instances will use the same session value. It also ensures properties of the same name from different components won't conflict.\n\nIf you want full control over what session key Livewire uses for a given property, you can pass the `key:` parameter:\n\n```php\n<?php\n\nuse Livewire\\Attributes\\Session;\nuse Livewire\\Component;\n\nclass ShowPosts extends Component\n{\n    #[Session(key: 'search')] // [tl! highlight]\n    public $search;\n\n    // ...\n}\n```\n\nWhen Livewire stores and retrieves the value of the `$search` property, it will use the given key: \"search\".\n\nAdditionally, if you want to generate the key dynamically from other properties in your component, you can do so using the following curly brace notation:\n\n```php\n<?php\n\nuse Livewire\\Attributes\\Session;\nuse Livewire\\Component;\nuse App\\Models\\Author;\n\nclass ShowPosts extends Component\n{\n    public Author $author;\n\n    #[Session(key: 'search-{author.id}')] // [tl! highlight]\n    public $search;\n\n    // ...\n}\n```\n\nIn the above example, if the `$author` model's id is \"4\", the session key will become: `search-4`\n\n---\n\n## Validation See also\nValidation | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/validation\n\n## See also\n\n- **[Forms](/docs/4.x/forms)** — Validate form inputs with real-time feedback\n- **[Properties](/docs/4.x/properties)** — Validate property values before persisting\n- **[Validate Attribute](/docs/4.x/attribute-validate)** — Use #[Validate] for property validation\n- **[Actions](/docs/4.x/actions)** — Validate data in action methods\n\n---\n\n## Tia\nTia | pestphp/pest@4.x | Source: https://github.com/pestphp/docs/blob/4.x/tia.md\n\n---\ntitle: Test Impact Analysis (TIA)\ndescription: Test Impact Analysis is a great way to drastically reduce the time it takes to run your test suite by re-running only the tests affected by your latest changes.\n---\n\n# Test Impact Analysis (TIA)\n\nTest Impact Analysis is a great way to drastically reduce the time it takes to run your test suite by re-running only the tests affected by your latest changes. The first time you run with `--tia`, Pest records a graph of which tests depend on which files. Every run after that, Pest looks at what you changed, runs only the tests that touched those files, and replays cached results for everything else.\n\nA typical Laravel suite that takes 15 seconds replays in under a second. Edits to a single Blade template re-run a handful of feature tests. Comment-only edits, formatter passes, and README touches re-run nothing at all.\n\nTo get started, just add the `--tia` flag to any Pest invocation:\n\n```bash\n./vendor/bin/pest --parallel --tia\n```\n\nThe first run is the **baseline** — Pest enables a coverage driver (PCOV or Xdebug) and records the dependency graph as your tests execute. Expect a small overhead on this run only.\n\nEvery subsequent run is a **replay**. Pest compares your working tree against the baseline and re-runs only the tests affected by your changes:\n\n```\nTests:    774 passed (2658 assertions, 7 affected, 767 replayed)\nDuration: 0.74s\n```\n\n`affected` is the set of tests Pest actually executed. `replayed` is the set whose results were served from cache.\n\n## How Pest Decides What To Run\n\nFor each file you've changed, Pest looks for tests that depend on it:\n\n- **PHP source files** — your `app/` classes, controllers, models, helpers — are tracked through the coverage driver. A change to `app/Models/User.php` re-runs only the tests that touched `User`.\n- **Migrations** are intersected with the tables each test queried during the baseline. A column rename in `create_users_table.php` re-runs only the tests that queried the `users` table.\n- **Inertia pages** under `resources/js/Pages` re-run only the tests that server-side-rendered them.\n- **Shared JS components** under `resources/js/Components`, `Layouts`, and friends re-run only the tests whose pages import them — Pest walks Vite's module graph to figure this out.\n- **Blade templates** re-run only the tests that rendered them, including renders triggered by browser tests.\n- **Anything else** — config files, route files, `.env`, files outside the recorded graph — falls through to a broader pattern. Editing `config/app.php` re-runs every test, because Pest can't statically prove which tests depend on it.\n\n## Cosmetic Edits Don't Run Anything\n\nPest normalises file content before comparing, so cosmetic changes don't trigger any tests. PHP files have whitespace, line comments, and docblocks stripped before hashing. Blade strips `{{-- … --}}` comments. JS, TS, Vue, and Svelte files have line and block comments removed too.\n\nThe result: a comment-only edit, a Prettier reformat, a Pint pass, or a README tweak produces an identical hash, and the file never enters the changed set. Zero tests run.\n\n## Modes\n\nPest supports a few flags alongside `--tia`:\n\n| Flag | Behaviour |\n|---|---|\n| `--tia` | Replay if a baseline graph exists, otherwise record. |\n| `--tia --fresh` | Discard any existing graph and re-record from scratch. Use this after large refactors or when the graph feels stale. |\n| `--tia --refetch` | Force a CI baseline fetch even within the 24-hour cooldown after a previous failed fetch. |\n| `--tia --filtered` | Narrow PHPUnit to only the affected test files rather than loading all tests and replaying cached results for unaffected ones. |\n\n## Sharing The Baseline From CI\n\nRecording the baseline locally takes minutes on large suites. Instead, you can have CI record it once per merge to `main`, and every developer downloads the result.\n\nWhen Pest detects no local graph (or the local graph is out of date), it uses GitHub's CLI to download the latest successful run of a `tia-baseline.yml` workflow's `pest-tia-baseline` artifact. Pest validates the fetched graph against your project state — if it matches, it's adopted. Otherwise it's discarded and a local rebuild proceeds.\n\nHere's a starter workflow you can drop into `.github/workflows/tia-baseline.yml`:\n\n```yaml\nname: TIA Baseline\non:\n  push: { branches: [main] }\n  schedule: [{ cron: '0 3 * * *' }]\n  workflow_dispatch:\njobs:\n  baseline:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n        with: { fetch-depth: 0 }\n      - uses: shivammathur/setup-php@v2\n        with: { php-version: '8.4', coverage: xdebug }\n      - run: composer install --no-interaction --prefer-dist\n      - run: ./vendor/bin/pest --parallel --tia --coverage\n      - name: Stage baseline for upload\n        shell: bash\n        run: |\n          mkdir -p .pest-tia-baseline\n          cp -R \"$HOME/.pest/tia\"/*/. .pest-tia-baseline/\n      - uses: actions/upload-artifact@v4\n        with:\n          name: pest-tia-baseline\n          path: .pest-tia-baseline/\n          retention-days: 30\n```\n\nAfter CI runs, every developer who runs `pest --tia` for the first time on the repo will download this baseline and start replaying immediately, paying no record cost.\n\n## Storage\n\nPest stores its state at `~/.pest/tia/<project-key>/`, where the project key is derived from your normalised git remote URL — so `git@github.com:foo/bar.git` and `https://github.com/foo/bar` produce the same key. A non-git project falls back to a hash of the project's absolute path.\n\nSharing state per remote URL means multiple worktrees of the same repository share one cache, while unrelated projects on the same machine stay isolated.\n\n## Configuration\n\nYou can configure TIA behaviour in `tests/Pest.php` via `pest()->tia()`:\n\n```php\npest()->tia()\n    ->always()   // run TIA on every invocation, no --tia flag needed\n    ->locally(); // restrict always() to local environments only\n```\n\n**`always()`** activates TIA for every `pest` run without requiring the `--tia` flag. Pair it with **`locally()`** to restrict that behaviour to local machines — on CI (detected via the `--ci` flag or the `CI` environment variable) TIA is skipped automatically. An explicit `--tia` on the command line always takes effect regardless.\n\n**`filtered()`** enables filtered mode, equivalent to `--tia --filtered`. In this mode Pest narrows PHPUnit to only the affected test files rather than loading the full suite and replaying cached results for unaffected tests:\n\n```php\npest()->tia()->filtered();\n```\n\n## Custom Watch Patterns\n\nIf your project has a directory layout that doesn't match the framework defaults, you can register custom watch patterns in `tests/Pest.php`:\n\n```php\npest()->tia()->watch([\n    'config/billing/**/*.php' => 'tests/Feature/Billing',\n    'public/build/**/*'       => 'tests/Browser',\n]);\n```\n\nEach glob maps to a single test directory. Whenever a matching file changes, every test under that directory is invalidated. Duplicate glob keys overwrite the built-in defaults, so you can redirect a pattern to a narrower directory.\n\n---\n\nNow that you've learned how to use Test Impact Analysis to speed up your test suite, let's discuss how to integrate Pest with your continuous integration workflow: [Continuous Integration](/docs/continuous-integration)\n\n\n---\n\n## Validation Form objects\nValidation | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/validation\n\n## Form objects\n\nAs more properties and validation rules are added to a Livewire component, it can begin to feel too crowded. To alleviate this pain and also provide a helpful abstraction for code reuse, you can use Livewire's *Form Objects* to store your properties and validation rules.\n\nBelow is the same `CreatePost` example, but now the properties and rules have been extracted to a dedicated form object named `PostForm`:\n\n```php\n<?php\n\nnamespace App\\Livewire\\Forms;\n\nuse Livewire\\Attributes\\Validate;\nuse Livewire\\Form;\n\nclass PostForm extends Form\n{\n    #[Validate('required|min:3')]\n\tpublic $title = '';\n\n    #[Validate('required|min:3')]\n    public $content = '';\n}\n```\n\nThe `PostForm` above can now be defined as a property on the `CreatePost` component:\n\n```php\n<?php\n\nnamespace App\\Livewire;\n\nuse App\\Livewire\\Forms\\PostForm;\nuse Livewire\\Component;\nuse App\\Models\\Post;\n\nclass CreatePost extends Component\n{\n    public PostForm $form;\n\n    public function save()\n    {\n\t\tPost::create(\n    \t\t$this->form->all()\n    \t);\n\n\t\treturn redirect()->to('/posts');\n    }\n\n    // ...\n}\n```\n\nAs you can see, instead of listing out each property individually, we can retrieve all the property values using the `->all()` method on the form object.\n\nAlso, when referencing the property names in the template, you must prepend `form.` to each instance:\n\n```blade\n<form wire:submit=\"save\">\n\t<input type=\"text\" wire:model=\"form.title\">\n    <div>@error('form.title') {{ $message }} @enderror</div>\n\n\t<textarea wire:model=\"form.content\"></textarea>\n    <div>@error('form.content') {{ $message }} @enderror</div>\n\n\t<button type=\"submit\">Save</button>\n</form>\n```\n\nWhen using form objects, `#[Validate]` attribute validation will be run every time a property is updated. However, if you disable this behavior by specifying `onUpdate: false` on the attribute, you can manually run a form object's validation using `$this->form->validate()`:\n\n```php\npublic function save()\n{\n    Post::create(\n        $this->form->validate()\n    );\n\n    return redirect()->to('/posts');\n}\n```\n\nForm objects are a useful abstraction for most larger datasets and a variety of additional features that make them even more powerful. For more information, check out the comprehensive [form object documentation](/docs/4.x/forms#extracting-a-form-object).\n\n---\n\n## Session Properties Basic usage\nSession Properties | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/session-properties\n\n## Basic usage\n\nHere's a `show-posts` component that allows users to filter visible posts by a string stored in a `$search` property:\n\n```php\n<?php // resources/views/components/⚡show-posts.blade.php\n\nuse Livewire\\Attributes\\Computed;\nuse Livewire\\Attributes\\Session;\nuse Livewire\\Component;\nuse App\\Models\\Post;\n\nnew class extends Component {\n    #[Session] // [tl! highlight]\n    public $search;\n\n    #[Computed]\n    public function posts()\n    {\n        return $this->search === ''\n            ? Post::all()\n            : Post::where('title', 'like', '%'.$this->search.'%');\n    }\n};\n```\n\nBecause the `#[Session]` attribute has been added to the `$search` property, after a user enters a search value, they can refresh the page and the search value will be persisted. Every time `$search` is updated, its new value will be stored in the user's session and used across page loads.\n\n> [!warning] Performance implications\n> Because Laravel sessions are loaded into memory during every request, you can slow down the performance of your entire application for a given user by storing too much in a user's session.\n\n---\n\n## Undocumented Features Todo High Priority\nUndocumented Features Todo | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/undocumented-features-todo\n\n## High Priority\n\n1. **`wire:intersect` directive**\n   - Full API with all modifiers (`.once`, `.half`, `.full`, `.threshold.X`, `.margin.Xpx`)\n   - Usage patterns and examples\n   - Comparison with Alpine's `x-intersect`\n   - Suggested location: New docs page or section in wire-* directives\n\n2. **`data-loading` attribute**\n   - Automatic addition to elements that trigger requests\n   - Styling examples and use cases\n   - Suggested location: Section in wire-loading.md or actions.md\n\n3. **`wire:preserve-scroll` modifier**\n   - How it works and when to use it\n   - Examples of preventing layout jumps\n   - Suggested location: Actions.md or new wire-preserve-scroll.md\n\n4. **`$errors` magic property in JavaScript**\n   - Full API (`.has()`, `.first()`, `.all()`, etc.)\n   - Integration with Alpine\n   - Suggested location: Forms.md or validation.md\n\n5. **`$island()` magic action**\n   - Calling island renders from templates\n   - Passing modes and options\n   - Suggested location: Islands.md (add a section)\n\n6. **`$wire.$intercept()` magic**\n   - Full API and callback structure\n   - Use cases and examples\n   - Suggested location: JavaScript.md (interceptors section)\n\n7. **JavaScript modules in view-based components**\n   - How `<script>` tags work without `@script` wrapper\n   - Automatic `$wire` binding as `this`\n   - Module caching and performance benefits\n   - Suggested location: Components.md or JavaScript.md\n\n8. **CSP-safe mode restrictions**\n   - Complete list of what expressions are not allowed\n   - Migration guide for existing apps\n   - Workarounds and alternatives\n   - Suggested location: New security.md section or dedicated csp.md page\n\n---\n\n## Lazy Full-page lazy loading\nLazy | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/lazy\n\n## Full-page lazy loading\n\nYou can lazy load or defer full-page Livewire components using route methods.\n\n### Lazy loading full pages\n\nUse `->lazy()` to load the component when it enters the viewport:\n\n```php\nRoute::livewire('/dashboard', 'pages::dashboard')->lazy();\n```\n\n### Deferring full pages\n\nUse `->defer()` to load the component immediately after the page loads:\n\n```php\nRoute::livewire('/dashboard', 'pages::dashboard')->defer();\n```\n\n### Disabling lazy/defer loading\n\nIf a component is lazy or deferred by default (via the `#[Lazy]` or `#[Defer]` attribute), you can opt-out using `enabled: false`:\n\n```php\nRoute::livewire('/dashboard', 'pages::dashboard')->lazy(enabled: false);\nRoute::livewire('/dashboard', 'pages::dashboard')->defer(enabled: false);\n```\n\n---\n\n## Session Interacting With the Session Retrieving Data The Global Session Helper\nSession | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/session.md\n\n\n#### The Global Session Helper\n\nYou may also use the global `session` PHP function to retrieve and store data in the session. When the `session` helper is called with a single, string argument, it will return the value of that session key. When the helper is called with an array of key / value pairs, those values will be stored in the session:\n\n```php\nRoute::get('/home', function () {\n    // Retrieve a piece of data from the session...\n    $value = session('key');\n\n    // Specifying a default value...\n    $value = session('key', 'default');\n\n    // Store a piece of data in the session...\n    session(['key' => 'value']);\n});\n```\n\n> [!NOTE]\n> There is little practical difference between using the session via an HTTP request instance versus using the global `session` helper. Both methods are [testable](/docs/{{version}}/testing) via the `assertSessionHas` method which is available in all of your test cases.\n\n---\n\n## Redirecting Redirecting to full-page components\nRedirecting | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/redirecting\n\n## Redirecting to full-page components\n\nBecause Livewire uses Laravel's built-in redirection feature, you can use all of the redirection methods available to you in a typical Laravel application.\n\nFor example, if you are using a Livewire component as a full-page component for a route like so:\n\n```php\nRoute::livewire('/posts', 'pages::show-posts');\n```\n\nYou can redirect to it simply by using the route path:\n\n```php\npublic function save()\n{\n    // ...\n\n    $this->redirect('/posts');\n}\n```\n\n---\n\n## Testing See also\nTesting | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/testing\n\n## See also\n\n- **[Actions](/docs/4.x/actions)** — Test component actions and interactions\n- **[Forms](/docs/4.x/forms)** — Test form submissions and validation\n- **[Events](/docs/4.x/events)** — Test event dispatching and listening\n- **[Components](/docs/4.x/components)** — Create testable component structure\n\n---\n\n## Attribute Title Only for full-page components\nAttribute Title | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/attribute-title\n\n## Only for full-page components\n\n> [!info] Full-page components only\n> The `#[Title]` attribute only works for full-page components accessed via routes. Regular components rendered within other views don't use titles—they inherit the parent page's title.\n\n---\n\n## Session Properties\nSession Properties | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/session-properties\n\nLivewire makes it easy to persist property values across page refreshes/changes using the `#[Session]` attribute.\n\nBy adding `#[Session]` to a property in your component, Livewire will store that property's value in the session every time it changes. This way, when a page is refreshed, Livewire will fetch the latest value from the session and use it in your component.\n\nThe `#[Session]` attribute is analogous to the [`#[Url]`](/docs/4.x/url) attribute. They are both useful in similar scenarios. The primary difference being `#[Session]` persists values without modifying the URL's query string, which is sometimes desired; sometimes not.\n\n---\n\n## Understanding Nesting Performance implications\nUnderstanding Nesting | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/understanding-nesting\n\n## Performance implications\n\nLivewire's independent component architecture can have both positive and negative implications for your application.\n\nAn advantage of this architecture is it allows you to isolate expensive portions of your application. For example, you can quarantine a slow database query to its own independent component, and its performance overhead won't impact the rest of the page.\n\nHowever, the biggest drawback of this approach is that because components are entirely separate, inter-component communication/dependencies becomes more difficult.\n\nFor example, if you had a property passed down from the above parent `Posts` component to the nested `ShowPost` component, it wouldn't be \"reactive\". Because each component is independent, if a request to the parent component changed the value of the property being passed into `ShowPost`, it wouldn't update inside `ShowPost`.\n\nLivewire has overcome a number of these hurdles and provides dedicated APIs for these scenarios like: [Reactive properties](/docs/4.x/nesting#reactive-props), [Modelable components](/docs/4.x/nesting#binding-to-child-data-using-wiremodel), and [the `$parent` object](/docs/4.x/nesting#directly-accessing-the-parent-from-the-child).\n\nArmed with this knowledge of how nested Livewire components operate, you will be able to make more informed decisions about when and how to nest components within your application.\n\n---\n\n## Attribute Validate Learn more\nAttribute Validate | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/attribute-validate\n\n## Learn more\n\nFor comprehensive documentation on validation, including form objects, custom rules, and testing, see the [Validation documentation](/docs/4.x/validation).\n\n---\n\n## Forms See also\nForms | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/forms\n\n## See also\n\n- **[Validation](/docs/4.x/validation)** — Validate form inputs with real-time feedback\n- **[wire:model](/docs/4.x/wire-model)** — Bind form inputs to component properties\n- **[File Uploads](/docs/4.x/uploads)** — Handle file uploads in forms\n- **[Actions](/docs/4.x/actions)** — Process form submissions with actions\n- **[Loading States](/docs/4.x/loading-states)** — Show loading indicators during form submission\n\n---\n\n## Forms Submitting a form\nForms | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/forms\n\n## Submitting a form\n\nLet's start by looking at a very simple form in a `post.create` component. This form will have two simple text inputs and a submit button, as well as some code on the backend to manage the form's state and submission:\n\n```php\n<?php // resources/views/components/post/⚡create.blade.php\n\nuse Livewire\\Component;\nuse App\\Models\\Post;\n\nnew class extends Component {\n    public $title = '';\n\n    public $content = '';\n\n    public function save()\n    {\n        Post::create(\n            $this->only(['title', 'content'])\n        );\n\n        session()->flash('status', 'Post successfully updated.');\n\n        return $this->redirect('/posts');\n    }\n};\n?>\n\n<form wire:submit=\"save\">\n    <input type=\"text\" wire:model=\"title\">\n\n    <input type=\"text\" wire:model=\"content\">\n\n    <button type=\"submit\">Save</button>\n</form>\n```\n\nAs you can see, we are \"binding\" the public `$title` and `$content` properties in the form above using `wire:model`. This is one of the most commonly used and powerful features of Livewire.\n\nIn addition to binding `$title` and `$content`, we are using `wire:submit` to capture the `submit` event when the \"Save\" button is clicked and invoking the `save()` action. This action will persist the form input to the database.\n\nAfter the new post is created in the database, we redirect the user to the posts page and show them a \"flash\" message that the new post was created.\n\n### Adding validation\n\nTo avoid storing incomplete or dangerous user input, most forms need some sort of input validation.\n\nLivewire makes validating your forms as simple as adding `#[Validate]` attributes above the properties you want to be validated.\n\nOnce a property has a `#[Validate]` attribute attached to it, the validation rule will be applied to the property's value any time it's updated server-side.\n\nLet's add some basic validation rules to the `$title` and `$content` properties in our `post.create` component:\n\n```php\n<?php // resources/views/components/post/⚡create.blade.php\n\nuse Livewire\\Attributes\\Validate; // [tl! highlight]\nuse Livewire\\Component;\nuse App\\Models\\Post;\n\nnew class extends Component {\n    #[Validate('required')] // [tl! highlight]\n    public $title = '';\n\n    #[Validate('required')] // [tl! highlight]\n    public $content = '';\n\n    public function save()\n    {\n        $this->validate(); // [tl! highlight]\n\n        Post::create(\n            $this->only(['title', 'content'])\n        );\n\n        return $this->redirect('/posts');\n    }\n};\n```\n\nWe'll also modify our Blade template to show any validation errors on the page.\n\n```blade\n<form wire:submit=\"save\">\n    <input type=\"text\" wire:model=\"title\">\n    <div>\n        @error('title') <span class=\"error\">{{ $message }}</span> @enderror <!-- [tl! highlight] -->\n    </div>\n\n    <input type=\"text\" wire:model=\"content\">\n    <div>\n        @error('content') <span class=\"error\">{{ $message }}</span> @enderror <!-- [tl! highlight] -->\n    </div>\n\n    <button type=\"submit\">Save</button>\n</form>\n```\n\nNow, if the user tries to submit the form without filling in any of the fields, they will see validation messages telling them which fields are required before saving the post.\n\nLivewire has a lot more validation features to offer. For more information, visit our [dedicated documentation page on Validation](/docs/4.x/validation).\n\n### Extracting a form object\n\nIf you are working with a large form and prefer to extract all of its properties, validation logic, etc., into a separate class, Livewire offers form objects.\n\nForm objects allow you to re-use form logic across components and provide a nice way to keep your component class cleaner by grouping all form-related code into a separate class.\n\nYou can either create a form class by hand or use the convenient artisan command:\n\n```shell\nphp artisan livewire:form PostForm\n```\n\nThe above command will create a file called `app/Livewire/Forms/PostForm.php`.\n\nLet's rewrite the `post.create` component to use a `PostForm` class:\n\n```php\n<?php\n\nnamespace App\\Livewire\\Forms;\n\nuse Livewire\\Attributes\\Validate;\nuse Livewire\\Form;\n\nclass PostForm extends Form\n{\n    #[Validate('required|min:5')]\n    public $title = '';\n\n    #[Validate('required|min:5')]\n    public $content = '';\n}\n```\n\n```php\n<?php // resources/views/components/post/⚡create.blade.php\n\nuse App\\Livewire\\Forms\\PostForm;\nuse Livewire\\Component;\nuse App\\Models\\Post;\n\nnew class extends Component {\n    public PostForm $form; // [tl! highlight]\n\n    public function save()\n    {\n        $this->validate();\n\n        Post::create(\n            $this->form->only(['title', 'content']) // [tl! highlight]\n        );\n\n        return $this->redirect('/posts');\n    }\n};\n```\n\n```blade\n<form wire:submit=\"save\">\n    <input type=\"text\" wire:model=\"form.title\">\n    <div>\n        @error('form.title') <span class=\"error\">{{ $message }}</span> @enderror\n    </div>\n\n    <input type=\"text\" wire:model=\"form.content\">\n    <div>\n        @error('form.content') <span class=\"error\">{{ $message }}</span> @enderror\n    </div>\n\n    <button type=\"submit\">Save</button>\n</form>\n```\n\nIf you'd like, you can also extract the post creation logic into the form object like so:\n\n```php\n<?php\n\nnamespace App\\Livewire\\Forms;\n\nuse Livewire\\Attributes\\Validate;\nuse App\\Models\\Post;\nuse Livewire\\Form;\n\nclass PostForm extends Form\n{\n    #[Validate('required|min:5')]\n    public $title = '';\n\n    #[Validate('required|min:5')]\n    public $content = '';\n\n    public function store() // [tl! highlight:5]\n    {\n        $this->validate();\n\n        Post::create($this->only(['title', 'content']));\n    }\n}\n```\n\nNow you can call `$this->form->store()` from the component:\n\n```php\n<?php // resources/views/components/post/⚡create.blade.php\n\nuse App\\Livewire\\Forms\\PostForm;\nuse Livewire\\Component;\n\nnew class extends Component {\n    public PostForm $form;\n\n    public function save()\n    {\n        $this->form->store(); // [tl! highlight]\n\n        return $this->redirect('/posts');\n    }\n\n    // ...\n};\n```\n\nIf you want to use this form object for both a create and update form, you can easily adapt it to handle both use cases.\n\nHere's what it would look like to use this same form object for a `post.edit` component and fill it with initial data:\n\n```php\n<?php // resources/views/components/post/⚡edit.blade.php\n\nuse App\\Livewire\\Forms\\PostForm;\nuse Livewire\\Component;\nuse App\\Models\\Post;\n\nnew class extends Component {\n    public PostForm $form;\n\n    public function mount(Post $post)\n    {\n        $this->form->setPost($post);\n    }\n\n    public function save()\n    {\n        $this->form->update();\n\n        return $this->redirect('/posts');\n    }\n};\n```\n\n```php\n<?php\n\nnamespace App\\Livewire\\Forms;\n\nuse Livewire\\Attributes\\Validate;\nuse Livewire\\Form;\nuse App\\Models\\Post;\n\nclass PostForm extends Form\n{\n    public ?Post $post;\n\n    #[Validate('required|min:5')]\n    public $title = '';\n\n    #[Validate('required|min:5')]\n    public $content = '';\n\n    public function setPost(Post $post)\n    {\n        $this->post = $post;\n\n        $this->title = $post->title;\n\n        $this->content = $post->content;\n    }\n\n    public function store()\n    {\n        $this->validate();\n\n        Post::create($this->only(['title', 'content']));\n    }\n\n    public function update()\n    {\n        $this->validate();\n\n        $this->post->update(\n            $this->only(['title', 'content'])\n        );\n    }\n}\n```\n\nAs you can see, we've added a `setPost()` method to the `PostForm` object to optionally allow for filling the form with existing data as well as storing the post on the form object for later use. We've also added an `update()` method for updating the existing post.\n\nForm objects are not required when working with Livewire, but they do offer a nice abstraction for keeping your components free of repetitive boilerplate.\n\n### Resetting form fields\n\nIf you are using a form object, you may want to reset the form after it has been submitted. This can be done by calling the `reset()` method:\n\n```php\n<?php\n\nnamespace App\\Livewire\\Forms;\n\nuse Livewire\\Attributes\\Validate;\nuse App\\Models\\Post;\nuse Livewire\\Form;\n\nclass PostForm extends Form\n{\n    #[Validate('required|min:5')]\n    public $title = '';\n\n    #[Validate('required|min:5')]\n    public $content = '';\n\n    // ...\n\n    public function store()\n    {\n        $this->validate();\n\n        Post::create($this->only(['title', 'content']));\n\n        $this->reset(); // [tl! highlight]\n    }\n}\n```\n\nYou can also reset specific properties by passing the property names into the `reset()` method:\n\n```php\n$this->reset('title');\n\n// Or multiple at once...\n\n$this->reset(['title', 'content']);\n```\n\n### Pulling form fields\n\nAlternatively, you can use the `pull()` method to both retrieve a form's properties and reset them in one operation.\n\n```php\n<?php\n\nnamespace App\\Livewire\\Forms;\n\nuse Livewire\\Attributes\\Validate;\nuse App\\Models\\Post;\nuse Livewire\\Form;\n\nclass PostForm extends Form\n{\n    #[Validate('required|min:5')]\n    public $title = '';\n\n    #[Validate('required|min:5')]\n    public $content = '';\n\n    // ...\n\n    public function store()\n    {\n        $this->validate();\n\n        Post::create(\n            $this->pull() // [tl! highlight]\n        );\n    }\n}\n```\n\nYou can also pull specific properties by passing the property names into the `pull()` method:\n\n```php\n// Return a value before resetting...\n$this->pull('title');\n\n // Return a key-value array of properties before resetting...\n$this->pull(['title', 'content']);\n```\n\n### Using Rule objects\n\nIf you have more sophisticated validation scenarios where Laravel's `Rule` objects are necessary, you can alternatively define a `rules()` method to declare your validation rules like so:\n\n```php\n<?php\n\nnamespace App\\Livewire\\Forms;\n\nuse Illuminate\\Validation\\Rule;\nuse App\\Models\\Post;\nuse Livewire\\Form;\n\nclass PostForm extends Form\n{\n    public ?Post $post;\n\n    public $title = '';\n\n    public $content = '';\n\n    protected function rules()\n    {\n        return [\n            'title' => [\n                'required',\n                Rule::unique('posts')->ignore($this->post), // [tl! highlight]\n            ],\n            'content' => 'required|min:5',\n        ];\n    }\n\n    // ...\n\n    public function update()\n    {\n        $this->validate();\n\n        $this->post->update($this->only(['title', 'content']));\n\n        $this->reset();\n    }\n}\n```\n\nWhen using a `rules()` method instead of `#[Validate]`, Livewire will only run the validation rules when you call `$this->validate()`, rather than every time a property is updated.\n\nIf you are using real-time validation or any other scenario where you'd like Livewire to validate specific fields after every request, you can use `#[Validate]` without any provided rules like so:\n\n```php\n<?php\n\nnamespace App\\Livewire\\Forms;\n\nuse Livewire\\Attributes\\Validate;\nuse Illuminate\\Validation\\Rule;\nuse App\\Models\\Post;\nuse Livewire\\Form;\n\nclass PostForm extends Form\n{\n    public ?Post $post;\n\n    #[Validate] // [tl! highlight]\n    public $title = '';\n\n    public $content = '';\n\n    protected function rules()\n    {\n        return [\n            'title' => [\n                'required',\n                Rule::unique('posts')->ignore($this->post),\n            ],\n            'content' => 'required|min:5',\n        ];\n    }\n\n    // ...\n\n    public function update()\n    {\n        $this->validate();\n\n        $this->post->update($this->only(['title', 'content']));\n\n        $this->reset();\n    }\n}\n```\n\nNow if the `$title` property is updated before the form is submitted—like when using [`wire:model.live.blur`](/docs/4.x/wire-model#updating-on-blur-event)—the validation for `$title` will be run.\n\n### Showing a loading indicator\n\nBy default, Livewire will automatically disable submit buttons and mark inputs as `readonly` while a form is being submitted, preventing the user from submitting the form again while the first submission is being handled.\n\nHowever, it can be difficult for users to detect this \"loading\" state without extra affordances in your application's UI.\n\nHere's an example of adding a small loading spinner to the \"Save\" button via `wire:loading` so that a user understands that the form is being submitted:\n\n```blade\n<button type=\"submit\">\n    Save\n\n    <div wire:loading>\n         <!-- SVG loading spinner -->\n    </span>\n</button>\n```\n\n[Learn more about loading states →](/docs/4.x/loading-states)\n\n_[Truncated]_"}]


Docs confirm this app should keep using routed Livewire page components plus normal action validation and Flux form controls. I’m reading the existing storefront routing/layout/component patterns next so the cart work fits the current shape.


rg --files app/Livewire resources/views routes config | sort


sed -n '1,260p' resources/views/layouts/storefront.blade.php


/bin/zsh
-lc
sed -n '1,260p' routes/web.php

<?php

use App\Livewire\Admin\Auth\Login as AdminLogin;
use App\Livewire\Admin\Collections\Form as AdminCollectionForm;
use App\Livewire\Admin\Collections\Index as AdminCollectionsIndex;
use App\Livewire\Admin\Inventory\Index as AdminInventoryIndex;
use App\Livewire\Admin\Products\Form as AdminProductForm;
use App\Livewire\Admin\Products\Index as AdminProductsIndex;
use App\Livewire\Storefront\Account\Auth\Login as CustomerLogin;
use App\Livewire\Storefront\Account\Auth\Register as CustomerRegister;
use App\Livewire\Storefront\Collections\Index as StorefrontCollectionsIndex;
use App\Livewire\Storefront\Collections\Show as StorefrontCollectionShow;
use App\Livewire\Storefront\Home as StorefrontHome;
use App\Livewire\Storefront\Pages\Show as StorefrontPageShow;
use App\Livewire\Storefront\Products\Show as StorefrontProductShow;
use App\Livewire\Storefront\Search\Index as StorefrontSearchIndex;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Route;

Route::middleware(['storefront'])->group(function (): void {
    Route::livewire('/', StorefrontHome::class)->name('home');
    Route::livewire('collections', StorefrontCollectionsIndex::class)->name('collections.index');
    Route::livewire('collections/{handle}', StorefrontCollectionShow::class)->name('collections.show');
    Route::livewire('products/{handle}', StorefrontProductShow::class)->name('products.show');
    Route::livewire('search', StorefrontSearchIndex::class)->name('search.index');
    Route::livewire('pages/{handle}', StorefrontPageShow::class)->name('pages.show');
});

Route::livewire('admin/login', AdminLogin::class)
    ->middleware('guest')
    ->name('admin.login');

Route::post('admin/logout', function () {
    Auth::guard('web')->logout();

    request()->session()->invalidate();
    request()->session()->regenerateToken();

    return redirect()->route('admin.login');
})->middleware('auth')->name('admin.logout');

Route::middleware(['auth', 'verified', 'admin'])->prefix('admin')->name('admin.')->group(function (): void {
    Route::view('/', 'dashboard')->name('dashboard');
    Route::livewire('products', AdminProductsIndex::class)->name('products.index');
    Route::livewire('products/create', AdminProductForm::class)->name('products.create');
    Route::livewire('products/{product}/edit', AdminProductForm::class)->name('products.edit');
    Route::livewire('inventory', AdminInventoryIndex::class)->name('inventory.index');
    Route::livewire('collections', AdminCollectionsIndex::class)->name('collections.index');
    Route::livewire('collections/create', AdminCollectionForm::class)->name('collections.create');
    Route::livewire('collections/{collection}/edit', AdminCollectionForm::class)->name('collections.edit');
});

Route::middleware(['storefront'])->group(function (): void {
    Route::livewire('account/login', CustomerLogin::class)
        ->middleware('guest:customer')
        ->name('account.login');

    Route::livewire('account/register', CustomerRegister::class)
        ->middleware('guest:customer')
        ->name('account.register');

    Route::view('account', 'storefront.account.dashboard')
        ->middleware('auth:customer')
        ->name('account.dashboard');
});

Route::redirect('dashboard', 'admin')
    ->middleware(['auth', 'verified'])
    ->name('dashboard');

require __DIR__.'/settings.php';


/bin/zsh
-lc
sed -n '1,220p' config/livewire.php

sed: config/livewire.php: No such file or directory


sed -n '1,260p' resources/views/layouts/storefront.blade.php

<!DOCTYPE html>
getLocale()) }}">

        @include('partials.head')

        @php($store = app()->bound('current_store') ? app('current_store') : null)
        @php($announcement = data_get($themeSettings ?? [], 'announcement', []))
        @php($mainLinks = ($mainNavigation ?? []) !== [] ? $mainNavigation : [['label' => 'Collections', 'url' => route('collections.index'), 'external' => false], ['label' => 'Search', 'url' => route('search.index'), 'external' => false]])
        @php($footerLinks = ($footerNavigation ?? []) !== [] ? $footerNavigation : [['label' => 'Collections', 'url' => route('collections.index'), 'external' => false], ['label' => 'Search', 'url' => route('search.index'), 'external' => false]])

            Skip to main content

        @if (data_get($announcement, 'enabled', false))

                @if (data_get($announcement, 'url'))

                        {{ data_get($announcement, 'text') }}

                @else
                    {{ data_get($announcement, 'text') }}
                @endif

        @endif

         data_get($themeSettings ?? [], 'header.sticky', true),
        ])>

                    {{ $store?->name ?? config('app.name') }}

                    @foreach ($mainLinks as $item)

                            {{ $item['label'] }}

                    @endforeach

                    <flux:button :href="route('search.index')" wire:navigate variant="subtle" icon="magnifying-glass" aria-label="Search" />
                    <flux:button :href="route('account.dashboard')" wire:navigate variant="subtle" icon="user" aria-label="Account" />
                    <flux:button href="#" variant="subtle" icon="shopping-bag" aria-label="Cart" />

            {{ $slot }}

{{ $store?->name ?? config('app.name') }}

{{ data_get($themeSettings ?? [], 'footer.tagline') }}

Shop

                        @foreach ($footerLinks as $item)

                                {{ $item['label'] }}

                        @endforeach

Customer

                        Account
                        About

        @fluxScripts


rg --files app/Livewire resources/views routes config | sort

app/Livewire/Actions/Logout.php
app/Livewire/Admin/Auth/Login.php
app/Livewire/Admin/Collections/Form.php
app/Livewire/Admin/Collections/Index.php
app/Livewire/Admin/Inventory/Index.php
app/Livewire/Admin/Products/Form.php
app/Livewire/Admin/Products/Index.php
app/Livewire/Settings/Appearance.php
app/Livewire/Settings/DeleteUserForm.php
app/Livewire/Settings/Password.php
app/Livewire/Settings/Profile.php
app/Livewire/Settings/TwoFactor.php
app/Livewire/Settings/TwoFactor/RecoveryCodes.php
app/Livewire/Storefront/Account/Auth/Login.php
app/Livewire/Storefront/Account/Auth/Register.php
app/Livewire/Storefront/Collections/Index.php
app/Livewire/Storefront/Collections/Show.php
app/Livewire/Storefront/Home.php
app/Livewire/Storefront/Pages/Show.php
app/Livewire/Storefront/Products/Show.php
app/Livewire/Storefront/Search/Index.php
config/app.php
config/auth.php
config/cache.php
config/database.php
config/filesystems.php
config/fortify.php
config/logging.php
config/mail.php
config/queue.php
config/services.php
config/session.php
resources/views/components/action-message.blade.php
resources/views/components/app-logo-icon.blade.php
resources/views/components/app-logo.blade.php
resources/views/components/auth-header.blade.php
resources/views/components/auth-session-status.blade.php
resources/views/components/desktop-user-menu.blade.php
resources/views/components/placeholder-pattern.blade.php
resources/views/components/settings/layout.blade.php
resources/views/components/storefront/breadcrumbs.blade.php
resources/views/components/storefront/price.blade.php
resources/views/components/storefront/product-card.blade.php
resources/views/dashboard.blade.php
resources/views/flux/icon/book-open-text.blade.php
resources/views/flux/icon/chevrons-up-down.blade.php
resources/views/flux/icon/folder-git-2.blade.php
resources/views/flux/icon/layout-grid.blade.php
resources/views/flux/navlist/group.blade.php
resources/views/layouts/app.blade.php
resources/views/layouts/app/header.blade.php
resources/views/layouts/app/sidebar.blade.php
resources/views/layouts/auth.blade.php
resources/views/layouts/auth/card.blade.php
resources/views/layouts/auth/simple.blade.php
resources/views/layouts/auth/split.blade.php
resources/views/layouts/storefront.blade.php
resources/views/livewire/admin/auth/login.blade.php
resources/views/livewire/admin/collections/form.blade.php
resources/views/livewire/admin/collections/index.blade.php
resources/views/livewire/admin/inventory/index.blade.php
resources/views/livewire/admin/products/form.blade.php
resources/views/livewire/admin/products/index.blade.php
resources/views/livewire/auth/confirm-password.blade.php
resources/views/livewire/auth/forgot-password.blade.php
resources/views/livewire/auth/login.blade.php
resources/views/livewire/auth/register.blade.php
resources/views/livewire/auth/reset-password.blade.php
resources/views/livewire/auth/two-factor-challenge.blade.php
resources/views/livewire/auth/verify-email.blade.php
resources/views/livewire/settings/appearance.blade.php
resources/views/livewire/settings/delete-user-form.blade.php
resources/views/livewire/settings/password.blade.php
resources/views/livewire/settings/profile.blade.php
resources/views/livewire/settings/two-factor.blade.php
resources/views/livewire/settings/two-factor/recovery-codes.blade.php
resources/views/livewire/storefront/account/auth/login.blade.php
resources/views/livewire/storefront/account/auth/register.blade.php
resources/views/livewire/storefront/collections/index.blade.php
resources/views/livewire/storefront/collections/show.blade.php
resources/views/livewire/storefront/home.blade.php
resources/views/livewire/storefront/pages/show.blade.php
resources/views/livewire/storefront/products/show.blade.php
resources/views/livewire/storefront/search/index.blade.php
resources/views/partials/head.blade.php
resources/views/partials/settings-heading.blade.php
resources/views/storefront/account/dashboard.blade.php
resources/views/welcome.blade.php
routes/console.php
routes/settings.php
routes/web.php


Output:
Chunk ID: e28c15
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 1012
Output:
app/Livewire/Actions/Logout.php
app/Livewire/Admin/Auth/Login.php
app/Livewire/Admin/Collections/Form.php
app/Livewire/Admin/Collections/Index.php
app/Livewire/Admin/Inventory/Index.php
app/Livewire/Admin/Products/Form.php
app/Livewire/Admin/Products/Index.php
app/Livewire/Settings/Appearance.php
app/Livewire/Settings/DeleteUserForm.php
app/Livewire/Settings/Password.php
app/Livewire/Settings/Profile.php
app/Livewire/Settings/TwoFactor.php
app/Livewire/Settings/TwoFactor/RecoveryCodes.php
app/Livewire/Storefront/Account/Auth/Login.php
app/Livewire/Storefront/Account/Auth/Register.php
app/Livewire/Storefront/Collections/Index.php
app/Livewire/Storefront/Collections/Show.php
app/Livewire/Storefront/Home.php
app/Livewire/Storefront/Pages/Show.php
app/Livewire/Storefront/Products/Show.php
app/Livewire/Storefront/Search/Index.php
config/app.php
config/auth.php
config/cache.php
config/database.php
config/filesystems.php
config/fortify.php
config/logging.php
config/mail.php
config/queue.php
config/services.php
config/session.php
resources/views/components/action-message.blade.php
resources/views/components/app-logo-icon.blade.php
resources/views/components/app-logo.blade.php
resources/views/components/auth-header.blade.php
resources/views/components/auth-session-status.blade.php
resources/views/components/desktop-user-menu.blade.php
resources/views/components/placeholder-pattern.blade.php
resources/views/components/settings/layout.blade.php
resources/views/components/storefront/breadcrumbs.blade.php
resources/views/components/storefront/price.blade.php
resources/views/components/storefront/product-card.blade.php
resources/views/dashboard.blade.php
resources/views/flux/icon/book-open-text.blade.php
resources/views/flux/icon/chevrons-up-down.blade.php
resources/views/flux/icon/folder-git-2.blade.php
resources/views/flux/icon/layout-grid.blade.php
resources/views/flux/navlist/group.blade.php
resources/views/layouts/app.blade.php
resources/views/layouts/app/header.blade.php
resources/views/layouts/app/sidebar.blade.php
resources/views/layouts/auth.blade.php
resources/views/layouts/auth/card.blade.php
resources/views/layouts/auth/simple.blade.php
resources/views/layouts/auth/split.blade.php
resources/views/layouts/storefront.blade.php
resources/views/livewire/admin/auth/login.blade.php
resources/views/livewire/admin/collections/form.blade.php
resources/views/livewire/admin/collections/index.blade.php
resources/views/livewire/admin/inventory/index.blade.php
resources/views/livewire/admin/products/form.blade.php
resources/views/livewire/admin/products/index.blade.php
resources/views/livewire/auth/confirm-password.blade.php
resources/views/livewire/auth/forgot-password.blade.php
resources/views/livewire/auth/login.blade.php
resources/views/livewire/auth/register.blade.php
resources/views/livewire/auth/reset-password.blade.php
resources/views/livewire/auth/two-factor-challenge.blade.php
resources/views/livewire/auth/verify-email.blade.php
resources/views/livewire/settings/appearance.blade.php
resources/views/livewire/settings/delete-user-form.blade.php
resources/views/livewire/settings/password.blade.php
resources/views/livewire/settings/profile.blade.php
resources/views/livewire/settings/two-factor.blade.php
resources/views/livewire/settings/two-factor/recovery-codes.blade.php
resources/views/livewire/storefront/account/auth/login.blade.php
resources/views/livewire/storefront/account/auth/register.blade.php
resources/views/livewire/storefront/collections/index.blade.php
resources/views/livewire/storefront/collections/show.blade.php
resources/views/livewire/storefront/home.blade.php
resources/views/livewire/storefront/pages/show.blade.php
resources/views/livewire/storefront/products/show.blade.php
resources/views/livewire/storefront/search/index.blade.php
resources/views/partials/head.blade.php
resources/views/partials/settings-heading.blade.php
resources/views/storefront/account/dashboard.blade.php
resources/views/welcome.blade.php
routes/console.php
routes/settings.php
routes/web.php


Output:
Chunk ID: 854133
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 843
Output:
<?php

use App\Livewire\Admin\Auth\Login as AdminLogin;
use App\Livewire\Admin\Collections\Form as AdminCollectionForm;
use App\Livewire\Admin\Collections\Index as AdminCollectionsIndex;
use App\Livewire\Admin\Inventory\Index as AdminInventoryIndex;
use App\Livewire\Admin\Products\Form as AdminProductForm;
use App\Livewire\Admin\Products\Index as AdminProductsIndex;
use App\Livewire\Storefront\Account\Auth\Login as CustomerLogin;
use App\Livewire\Storefront\Account\Auth\Register as CustomerRegister;
use App\Livewire\Storefront\Collections\Index as StorefrontCollectionsIndex;
use App\Livewire\Storefront\Collections\Show as StorefrontCollectionShow;
use App\Livewire\Storefront\Home as StorefrontHome;
use App\Livewire\Storefront\Pages\Show as StorefrontPageShow;
use App\Livewire\Storefront\Products\Show as StorefrontProductShow;
use App\Livewire\Storefront\Search\Index as StorefrontSearchIndex;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Route;

Route::middleware(['storefront'])->group(function (): void {
    Route::livewire('/', StorefrontHome::class)->name('home');
    Route::livewire('collections', StorefrontCollectionsIndex::class)->name('collections.index');
    Route::livewire('collections/{handle}', StorefrontCollectionShow::class)->name('collections.show');
    Route::livewire('products/{handle}', StorefrontProductShow::class)->name('products.show');
    Route::livewire('search', StorefrontSearchIndex::class)->name('search.index');
    Route::livewire('pages/{handle}', StorefrontPageShow::class)->name('pages.show');
});

Route::livewire('admin/login', AdminLogin::class)
    ->middleware('guest')
    ->name('admin.login');

Route::post('admin/logout', function () {
    Auth::guard('web')->logout();

    request()->session()->invalidate();
    request()->session()->regenerateToken();

    return redirect()->route('admin.login');
})->middleware('auth')->name('admin.logout');

Route::middleware(['auth', 'verified', 'admin'])->prefix('admin')->name('admin.')->group(function (): void {
    Route::view('/', 'dashboard')->name('dashboard');
    Route::livewire('products', AdminProductsIndex::class)->name('products.index');
    Route::livewire('products/create', AdminProductForm::class)->name('products.create');
    Route::livewire('products/{product}/edit', AdminProductForm::class)->name('products.edit');
    Route::livewire('inventory', AdminInventoryIndex::class)->name('inventory.index');
    Route::livewire('collections', AdminCollectionsIndex::class)->name('collections.index');
    Route::livewire('collections/create', AdminCollectionForm::class)->name('collections.create');
    Route::livewire('collections/{collection}/edit', AdminCollectionForm::class)->name('collections.edit');
});

Route::middleware(['storefront'])->group(function (): void {
    Route::livewire('account/login', CustomerLogin::class)
        ->middleware('guest:customer')
        ->name('account.login');

    Route::livewire('account/register', CustomerRegister::class)
        ->middleware('guest:customer')
        ->name('account.register');

    Route::view('account', 'storefront.account.dashboard')
        ->middleware('auth:customer')
        ->name('account.dashboard');
});

Route::redirect('dashboard', 'admin')
    ->middleware(['auth', 'verified'])
    ->name('dashboard');

require __DIR__.'/settings.php';


Output:
Chunk ID: b3a700
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 1253
Output:
<!DOCTYPE html>
getLocale()) }}">

        @include('partials.head')

        @php($store = app()->bound('current_store') ? app('current_store') : null)
        @php($announcement = data_get($themeSettings ?? [], 'announcement', []))
        @php($mainLinks = ($mainNavigation ?? []) !== [] ? $mainNavigation : [['label' => 'Collections', 'url' => route('collections.index'), 'external' => false], ['label' => 'Search', 'url' => route('search.index'), 'external' => false]])
        @php($footerLinks = ($footerNavigation ?? []) !== [] ? $footerNavigation : [['label' => 'Collections', 'url' => route('collections.index'), 'external' => false], ['label' => 'Search', 'url' => route('search.index'), 'external' => false]])

            Skip to main content

        @if (data_get($announcement, 'enabled', false))

                @if (data_get($announcement, 'url'))

                        {{ data_get($announcement, 'text') }}

                @else
                    {{ data_get($announcement, 'text') }}
                @endif

        @endif

         data_get($themeSettings ?? [], 'header.sticky', true),
        ])>

                    {{ $store?->name ?? config('app.name') }}

                    @foreach ($mainLinks as $item)

                            {{ $item['label'] }}

                    @endforeach

                    <flux:button :href="route('search.index')" wire:navigate variant="subtle" icon="magnifying-glass" aria-label="Search" />
                    <flux:button :href="route('account.dashboard')" wire:navigate variant="subtle" icon="user" aria-label="Account" />
                    <flux:button href="#" variant="subtle" icon="shopping-bag" aria-label="Cart" />

            {{ $slot }}

{{ $store?->name ?? config('app.name') }}

{{ data_get($themeSettings ?? [], 'footer.tagline') }}

Shop

                        @foreach ($footerLinks as $item)

                                {{ $item['label'] }}

                        @endforeach

Customer

                        Account
                        About

        @fluxScripts


Output:
Chunk ID: 344e3d
Wall time: 0.0000 seconds
Process exited with code 1
Original token count: 13
Output:
sed: config/livewire.php: No such file or directory


sed -n '1,260p' app/Livewire/Storefront/Products/Show.php


sed -n '1,320p' resources/views/livewire/storefront/products/show.blade.php


sed -n '1,220p' resources/views/livewire/storefront/home.blade.php


sed -n '1,220p' app/Livewire/Storefront/Home.php


sed -n '1,260p' app/Livewire/Storefront/Products/Show.php

<?php

namespace App\Livewire\Storefront\Products;

use App\Enums\InventoryPolicy;
use App\Models\Product;
use App\Models\ProductOptionValue;
use App\Models\ProductVariant;
use Livewire\Component;

class Show extends Component
{
    public string $handle;

    /**
     * @var array<string, string>
     */
    public array $selectedOptions = [];

    public int $quantity = 1;

    public function mount(string $handle): void
    {
        $this->handle = $handle;

        $variant = $this->product()->variants->firstWhere('is_default', true)
            ?? $this->product()->variants->first();

        if ($variant instanceof ProductVariant) {
            $this->selectedOptions = $variant->optionValues
                ->mapWithKeys(fn (ProductOptionValue $value): array => [$value->option->name => $value->value])
                ->all();
        }
    }

    public function selectOption(string $optionName, string $value): void
    {
        $this->selectedOptions[$optionName] = $value;
        $this->quantity = 1;
    }

    public function increaseQuantity(): void
    {
        $variant = $this->selectedVariant();
        $max = $variant?->inventoryItem?->policy === InventoryPolicy::Deny
            ? max(1, $variant->inventoryItem->availableQuantity())
            : null;

        if ($max === null || $this->quantity < $max) {
            $this->quantity++;
        }
    }

    public function decreaseQuantity(): void
    {
        $this->quantity = max(1, $this->quantity - 1);
    }

    public function addToCart(): void
    {
        if (! $this->canAddToCart()) {
            return;
        }

        $this->dispatch('toast', type: 'success', message: __('Added to cart'));
    }

    public function product(): Product
    {
        return Product::query()
            ->with(['options.values', 'variants.inventoryItem', 'variants.optionValues.option', 'collections'])
            ->where('handle', $this->handle)
            ->where('status', 'active')
            ->whereNotNull('published_at')
            ->firstOrFail();
    }

    public function selectedVariant(): ?ProductVariant
    {
        $product = $this->product();

        if ($product->options->isEmpty()) {
            return $product->variants->first();
        }

        return $product->variants->first(function (ProductVariant $variant): bool {
            $options = $variant->optionValues
                ->mapWithKeys(fn (ProductOptionValue $value): array => [$value->option->name => $value->value])
                ->all();

            return $options === $this->selectedOptions;
        });
    }

    public function canAddToCart(): bool
    {
        $variant = $this->selectedVariant();

        if (! $variant instanceof ProductVariant || ! $variant->isPurchasable()) {
            return false;
        }

        $inventory = $variant->inventoryItem;

        return $inventory?->policy === InventoryPolicy::Continue
            || ($inventory?->availableQuantity() ?? 0) >= $this->quantity;
    }

    /**
     * @return array{message: string, class: string}
     */
    public function stockState(): array
    {
        $variant = $this->selectedVariant();
        $inventory = $variant?->inventoryItem;

        if (! $inventory) {
            return ['message' => 'Unavailable', 'class' => 'text-red-700 dark:text-red-300'];
        }

        if ($inventory->policy === InventoryPolicy::Continue && $inventory->availableQuantity() <= 0) {
            return ['message' => 'Available on backorder', 'class' => 'text-blue-700 dark:text-blue-300'];
        }

        if ($inventory->availableQuantity() <= 0) {
            return ['message' => 'Out of stock', 'class' => 'text-red-700 dark:text-red-300'];
        }

        if ($inventory->availableQuantity() <= 10) {
            return ['message' => "Only {$inventory->availableQuantity()} left in stock", 'class' => 'text-amber-700 dark:text-amber-300'];
        }

        return ['message' => 'In stock', 'class' => 'text-green-700 dark:text-green-300'];
    }

    public function render(): mixed
    {
        $product = $this->product();

        return view('livewire.storefront.products.show', [
            'product' => $product,
            'selectedVariant' => $this->selectedVariant(),
            'stockState' => $this->stockState(),
        ])->layout('layouts.storefront', [
            'title' => $product->title,
        ]);
    }
}