output:
Chunk ID: c2b777
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 1275
Output:
---
name: pest-testing
description: "Use this skill for Pest PHP testing in Laravel projects only. Trigger whenever any test is being written, edited, fixed, or refactored — including fixing tests that broke after a code change, adding assertions, converting PHPUnit to Pest, adding datasets, and TDD workflows. Always activate when the user asks how to write something in Pest, mentions test files or directories (tests/Feature, tests/Unit, tests/Browser), or needs browser testing, smoke testing multiple pages for JS errors, or architecture tests. Covers: test()/it()/expect() syntax, datasets, mocking, browser testing (visit/click/fill), smoke testing, arch(), Livewire component tests, RefreshDatabase, and all Pest 4 features. Do not use for factories, seeders, migrations, controllers, models, or non-test PHP code."
license: MIT
metadata:
  author: laravel
---

# Pest Testing 4

## Documentation

Use `search-docs` for detailed Pest 4 patterns and documentation.

## Basic Usage

### Creating Tests

All tests must be written using Pest. Use `php artisan make:test --pest {name}`.

### Test Organization

- Unit/Feature tests: `tests/Feature` and `tests/Unit` directories.
- Browser tests: `tests/Browser/` directory.
- Do NOT remove tests without approval - these are core application code.

### Basic Test Structure

Pest supports both `test()` and `it()` functions. Before writing new tests, check existing test files in the same directory to match the project's convention. Use `test()` if existing tests use `test()`, or `it()` if they use `it()`.

<!-- Basic Pest Test Example -->
```php
it('is true', function () {
    expect(true)->toBeTrue();
});
```

### Running Tests

- Run minimal tests with filter before finalizing: `php artisan test --compact --filter=testName`.
- Run all tests: `php artisan test --compact`.
- Run file: `php artisan test --compact tests/Feature/ExampleTest.php`.

## Assertions

Use specific assertions (`assertSuccessful()`, `assertNotFound()`) instead of `assertStatus()`:

<!-- Pest Response Assertion -->
```php
it('returns all', function () {
    $this->postJson('/api/docs', [])->assertSuccessful();
});
```

| Use | Instead of |
|-----|------------|
| `assertSuccessful()` | `assertStatus(200)` |
| `assertNotFound()` | `assertStatus(404)` |
| `assertForbidden()` | `assertStatus(403)` |

## Mocking

Import mock function before use: `use function Pest\Laravel\mock;`

## Datasets

Use datasets for repetitive tests (validation rules, etc.):

<!-- Pest Dataset Example -->
```php
it('has emails', function (string $email) {
    expect($email)->not->toBeEmpty();
})->with([
    'james' => 'james@laravel.com',
    'taylor' => 'taylor@laravel.com',
]);
```

## Pest 4 Features

| Feature | Purpose |
|---------|---------|
| Browser Testing | Full integration tests in real browsers |
| Smoke Testing | Validate multiple pages quickly |
| Visual Regression | Compare screenshots for visual changes |
| Test Sharding | Parallel CI runs |
| Architecture Testing | Enforce code conventions |

### Browser Test Example

Browser tests run in real browsers for full integration testing:

- Browser tests live in `tests/Browser/`.
- Use Laravel features like `Event::fake()`, `assertAuthenticated()`, and model factories.
- Use `RefreshDatabase` for clean state per test.
- Interact with page: click, type, scroll, select, submit, drag-and-drop, touch gestures.
- Test on multiple browsers (Chrome, Firefox, Safari) if requested.
- Test on different devices/viewports (iPhone 14 Pro, tablets) if requested.
- Switch color schemes (light/dark mode) when appropriate.
- Take screenshots or pause tests for debugging.

<!-- Pest Browser Test Example -->
```php
it('may reset the password', function () {
    Notification::fake();

    $this->actingAs(User::factory()->create());

    $page = visit('/sign-in');

    $page->assertSee('Sign In')
        ->assertNoJavaScriptErrors()
        ->click('Forgot Password?')
        ->fill('email', 'nuno@laravel.com')
        ->click('Send Reset Link')
        ->assertSee('We have emailed your password reset link!');

    Notification::assertSent(ResetPassword::class);
});
```

### Smoke Testing

Quickly validate multiple pages have no JavaScript errors:

<!-- Pest Smoke Testing Example -->
```php
$pages = visit(['/', '/about', '/contact']);

$pages->assertNoJavaScriptErrors()->assertNoConsoleLogs();
```

### Visual Regression Testing

Capture and compare screenshots to detect visual changes.

### Test Sharding

Split tests across parallel processes for faster CI runs.

### Architecture Testing

Pest 4 includes architecture testing (from Pest 3):

<!-- Architecture Test Example -->
```php
arch('controllers')
    ->expect('App\Http\Controllers')
    ->toExtendNothing()
    ->toHaveSuffix('Controller');
```

## Common Pitfalls

- Not importing `use function Pest\Laravel\mock;` before using mock
- Using `assertStatus(200)` instead of `assertSuccessful()`
- Forgetting datasets for repetitive validation tests
- Deleting tests without approval
- Forgetting `assertNoJavaScriptErrors()` in browser tests


message: Using `laravel-best-practices`, `livewire-development`, `fluxui-development`, `tailwindcss-development`, and `pest-testing` for this UI/API slice because it touches Laravel routes/services, Livewire components, Flux controls, Tailwind layouts, and tests.

phase: commentary


type: message

role: assistant

text: Using `laravel-best-practices`, `livewire-development`, `fluxui-development`, `tailwindcss-development`, and `pest-testing` for this UI/API slice because it touches Laravel routes/services, Livewire components, Flux controls, Tailwind layouts, and tests.


cmd: sed -n '1,220p' /Users/fabianwesner/Herd/shop/.agents/skills/fluxui-development/SKILL.md


cmd: sed -n '1,220p' /Users/fabianwesner/Herd/shop/.agents/skills/tailwindcss-development/SKILL.md


packages: livewire/livewire livewire/flux laravel/framework pestphp/pest

queries: full page components route livewire test


command: sed -n '1,220p' /Users/fabianwesner/Herd/shop/.agents/skills/fluxui-development/SKILL.md

SKILL.md

type: read

aggregated_output:
---
name: fluxui-development
description: "Use this skill for Flux UI development in Livewire applications only. Trigger when working with <flux:*> components, building or customizing Livewire component UIs, creating forms, modals, tables, or other interactive elements. Covers: flux: components (buttons, inputs, modals, forms, tables, date-pickers, kanban, badges, tooltips, etc.), component composition, Tailwind CSS styling, Heroicons/Lucide icon integration, validation patterns, responsive design, and theming. Do not use for non-Livewire frameworks or non-component styling."
license: MIT
metadata:
  author: laravel
---

# Flux UI Development

## Documentation

Use `search-docs` for detailed Flux UI patterns and documentation.

## Basic Usage

This project uses the free edition of Flux UI, which includes all free components and variants but not Pro components.

Flux UI is a component library for Livewire built with Tailwind CSS. It provides components that are easy to use and customize.

Use Flux UI components when available. Fall back to standard Blade components when no Flux component exists for your needs.

<!-- Basic Button -->
```blade
<flux:button variant="primary">Click me</flux:button>
```

## Available Components (Free Edition)

Available: avatar, badge, brand, breadcrumbs, button, callout, checkbox, dropdown, field, heading, icon, input, modal, navbar, otp-input, profile, radio, select, separator, skeleton, switch, text, textarea, tooltip

## Icons

Flux includes [Heroicons](https://heroicons.com/) as its default icon set. Search for exact icon names on the Heroicons site - do not guess or invent icon names.

<!-- Icon Button -->
```blade
<flux:button icon="arrow-down-tray">Export</flux:button>
```

For icons not available in Heroicons, use [Lucide](https://lucide.dev/). Import the icons you need with the Artisan command:

```bash
php artisan flux:icon crown grip-vertical github
```

## Common Patterns

### Form Fields

<!-- Form Field -->
```blade
<flux:field>
    <flux:label>Email</flux:label>
    <flux:input type="email" wire:model="email" />
    <flux:error name="email" />
</flux:field>
```

### Modals

<!-- Modal -->
```blade
<flux:modal wire:model="showModal">
    <flux:heading>Title</flux:heading>
    <p>Content</p>
</flux:modal>
```

## Verification

1. Check component renders correctly
2. Test interactive states
3. Verify mobile responsiveness

## Common Pitfalls

- Trying to use Pro-only components in the free edition
- Not checking if a Flux component exists before creating custom implementations
- Forgetting to use the `search-docs` tool for component-specific documentation
- Not following existing project patterns for Flux usage

status: completed


output:
Chunk ID: 14d290
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 675
Output:
---
name: fluxui-development
description: "Use this skill for Flux UI development in Livewire applications only. Trigger when working with <flux:*> components, building or customizing Livewire component UIs, creating forms, modals, tables, or other interactive elements. Covers: flux: components (buttons, inputs, modals, forms, tables, date-pickers, kanban, badges, tooltips, etc.), component composition, Tailwind CSS styling, Heroicons/Lucide icon integration, validation patterns, responsive design, and theming. Do not use for non-Livewire frameworks or non-component styling."
license: MIT
metadata:
  author: laravel
---

# Flux UI Development

## Documentation

Use `search-docs` for detailed Flux UI patterns and documentation.

## Basic Usage

This project uses the free edition of Flux UI, which includes all free components and variants but not Pro components.

Flux UI is a component library for Livewire built with Tailwind CSS. It provides components that are easy to use and customize.

Use Flux UI components when available. Fall back to standard Blade components when no Flux component exists for your needs.

<!-- Basic Button -->
```blade
<flux:button variant="primary">Click me</flux:button>
```

## Available Components (Free Edition)

Available: avatar, badge, brand, breadcrumbs, button, callout, checkbox, dropdown, field, heading, icon, input, modal, navbar, otp-input, profile, radio, select, separator, skeleton, switch, text, textarea, tooltip

## Icons

Flux includes [Heroicons](https://heroicons.com/) as its default icon set. Search for exact icon names on the Heroicons site - do not guess or invent icon names.

<!-- Icon Button -->
```blade
<flux:button icon="arrow-down-tray">Export</flux:button>
```

For icons not available in Heroicons, use [Lucide](https://lucide.dev/). Import the icons you need with the Artisan command:

```bash
php artisan flux:icon crown grip-vertical github
```

## Common Patterns

### Form Fields

<!-- Form Field -->
```blade
<flux:field>
    <flux:label>Email</flux:label>
    <flux:input type="email" wire:model="email" />
    <flux:error name="email" />
</flux:field>
```

### Modals

<!-- Modal -->
```blade
<flux:modal wire:model="showModal">
    <flux:heading>Title</flux:heading>
    <p>Content</p>
</flux:modal>
```

## Verification

1. Check component renders correctly
2. Test interactive states
3. Verify mobile responsiveness

## Common Pitfalls

- Trying to use Pro-only components in the free edition
- Not checking if a Flux component exists before creating custom implementations
- Forgetting to use the `search-docs` tool for component-specific documentation
- Not following existing project patterns for Flux usage


command: sed -n '1,220p' /Users/fabianwesner/Herd/shop/.agents/skills/tailwindcss-development/SKILL.md

aggregated_output:
---
name: tailwindcss-development
description: "Always invoke when the user's message includes 'tailwind' in any form. Also invoke for: building responsive grid layouts (multi-column card grids, product grids), flex/grid page structures (dashboards with sidebars, fixed topbars, mobile-toggle navs), styling UI components (cards, tables, navbars, pricing sections, forms, inputs, badges), adding dark mode variants, fixing spacing or typography, and Tailwind v3/v4 work. The core use case: writing or fixing Tailwind utility classes in HTML templates (Blade, JSX, Vue). Skip for backend PHP logic, database queries, API routes, JavaScript with no HTML/CSS component, CSS file audits, build tool configuration, and vanilla CSS."
license: MIT
metadata:
  author: laravel
---

# Tailwind CSS Development

## Documentation

Use `search-docs` for detailed Tailwind CSS v4 patterns and documentation.

## Basic Usage

- Use Tailwind CSS classes to style HTML. Check and follow existing Tailwind conventions in the project before introducing new patterns.
- Offer to extract repeated patterns into components that match the project's conventions (e.g., Blade, JSX, Vue).
- Consider class placement, order, priority, and defaults. Remove redundant classes, add classes to parent or child elements carefully to reduce repetition, and group elements logically.

## Tailwind CSS v4 Specifics

- Always use Tailwind CSS v4 and avoid deprecated utilities.
- `corePlugins` is not supported in Tailwind v4.

### CSS-First Configuration

In Tailwind v4, configuration is CSS-first using the `@theme` directive — no separate `tailwind.config.js` file is needed:

<!-- CSS-First Config -->
```css
@theme {
  --color-brand: oklch(0.72 0.11 178);
}
```

### Import Syntax

In Tailwind v4, import Tailwind with a regular CSS `@import` statement instead of the `@tailwind` directives used in v3:

<!-- v4 Import Syntax -->
```diff
- @tailwind base;
- @tailwind components;
- @tailwind utilities;
+ @import "tailwindcss";
```

### Replaced Utilities

Tailwind v4 removed deprecated utilities. Use the replacements shown below. Opacity values remain numeric.

| Deprecated | Replacement |
|------------|-------------|
| bg-opacity-* | bg-black/* |
| text-opacity-* | text-black/* |
| border-opacity-* | border-black/* |
| divide-opacity-* | divide-black/* |
| ring-opacity-* | ring-black/* |
| placeholder-opacity-* | placeholder-black/* |
| flex-shrink-* | shrink-* |
| flex-grow-* | grow-* |
| overflow-ellipsis | text-ellipsis |
| decoration-slice | box-decoration-slice |
| decoration-clone | box-decoration-clone |

## Spacing

Use `gap` utilities instead of margins for spacing between siblings:

<!-- Gap Utilities -->
```html
<div class="flex gap-8">
    <div>Item 1</div>
    <div>Item 2</div>
</div>
```

## Dark Mode

If existing pages and components support dark mode, new pages and components must support it the same way, typically using the `dark:` variant:

<!-- Dark Mode -->
```html
<div class="bg-white dark:bg-gray-900 text-gray-900 dark:text-white">
    Content adapts to color scheme
</div>
```

## Common Patterns

### Flexbox Layout

<!-- Flexbox Layout -->
```html
<div class="flex items-center justify-between gap-4">
    <div>Left content</div>
    <div>Right content</div>
</div>
```

### Grid Layout

<!-- Grid Layout -->
```html
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
    <div>Card 1</div>
    <div>Card 2</div>
    <div>Card 3</div>
</div>
```

## Common Pitfalls

- Using deprecated v3 utilities (bg-opacity-*, flex-shrink-*, etc.)
- Using `@tailwind` directives instead of `@import "tailwindcss"`
- Trying to use `tailwind.config.js` instead of CSS `@theme` directive
- Using margins for spacing between siblings instead of gap utilities
- Forgetting to add dark mode variants when the project uses dark mode


output:
Chunk ID: e6bf82
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 962
Output:
---
name: tailwindcss-development
description: "Always invoke when the user's message includes 'tailwind' in any form. Also invoke for: building responsive grid layouts (multi-column card grids, product grids), flex/grid page structures (dashboards with sidebars, fixed topbars, mobile-toggle navs), styling UI components (cards, tables, navbars, pricing sections, forms, inputs, badges), adding dark mode variants, fixing spacing or typography, and Tailwind v3/v4 work. The core use case: writing or fixing Tailwind utility classes in HTML templates (Blade, JSX, Vue). Skip for backend PHP logic, database queries, API routes, JavaScript with no HTML/CSS component, CSS file audits, build tool configuration, and vanilla CSS."
license: MIT
metadata:
  author: laravel
---

# Tailwind CSS Development

## Documentation

Use `search-docs` for detailed Tailwind CSS v4 patterns and documentation.

## Basic Usage

- Use Tailwind CSS classes to style HTML. Check and follow existing Tailwind conventions in the project before introducing new patterns.
- Offer to extract repeated patterns into components that match the project's conventions (e.g., Blade, JSX, Vue).
- Consider class placement, order, priority, and defaults. Remove redundant classes, add classes to parent or child elements carefully to reduce repetition, and group elements logically.

## Tailwind CSS v4 Specifics

- Always use Tailwind CSS v4 and avoid deprecated utilities.
- `corePlugins` is not supported in Tailwind v4.

### CSS-First Configuration

In Tailwind v4, configuration is CSS-first using the `@theme` directive — no separate `tailwind.config.js` file is needed:

<!-- CSS-First Config -->
```css
@theme {
  --color-brand: oklch(0.72 0.11 178);
}
```

### Import Syntax

In Tailwind v4, import Tailwind with a regular CSS `@import` statement instead of the `@tailwind` directives used in v3:

<!-- v4 Import Syntax -->
```diff
- @tailwind base;
- @tailwind components;
- @tailwind utilities;
+ @import "tailwindcss";
```

### Replaced Utilities

Tailwind v4 removed deprecated utilities. Use the replacements shown below. Opacity values remain numeric.

| Deprecated | Replacement |
|------------|-------------|
| bg-opacity-* | bg-black/* |
| text-opacity-* | text-black/* |
| border-opacity-* | border-black/* |
| divide-opacity-* | divide-black/* |
| ring-opacity-* | ring-black/* |
| placeholder-opacity-* | placeholder-black/* |
| flex-shrink-* | shrink-* |
| flex-grow-* | grow-* |
| overflow-ellipsis | text-ellipsis |
| decoration-slice | box-decoration-slice |
| decoration-clone | box-decoration-clone |

## Spacing

Use `gap` utilities instead of margins for spacing between siblings:

<!-- Gap Utilities -->
```html
<div class="flex gap-8">
    <div>Item 1</div>
    <div>Item 2</div>
</div>
```

## Dark Mode

If existing pages and components support dark mode, new pages and components must support it the same way, typically using the `dark:` variant:

<!-- Dark Mode -->
```html
<div class="bg-white dark:bg-gray-900 text-gray-900 dark:text-white">
    Content adapts to color scheme
</div>
```

## Common Patterns

### Flexbox Layout

<!-- Flexbox Layout -->
```html
<div class="flex items-center justify-between gap-4">
    <div>Left content</div>
    <div>Right content</div>
</div>
```

### Grid Layout

<!-- Grid Layout -->
```html
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
    <div>Card 1</div>
    <div>Card 2</div>
    <div>Card 3</div>
</div>
```

## Common Pitfalls

- Using deprecated v3 utilities (bg-opacity-*, flex-shrink-*, etc.)
- Using `@tailwind` directives instead of `@import "tailwindcss"`
- Trying to use `tailwind.config.js` instead of CSS `@theme` directive
- Using margins for spacing between siblings instead of gap utilities
- Forgetting to add dark mode variants when the project uses dark mode


server: laravel-boost

tool: search-docs

type: text

text:
# 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]_"}]


message: 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.


text: 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.


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


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


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

web.php

cmd: sed -n '1,260p' routes/web.php

aggregated_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';


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

livewire.php

cmd: sed -n '1,220p' config/livewire.php

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

status: failed