Output:
Chunk ID: c7fd23
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 1396
Output:
#### tests/Feature/Cart/CartServiceTest.php

| Test Name | Setup / Preconditions | Key Assertions | Category |
|-----------|----------------------|----------------|----------|
| creates a cart for the current store | create cart | cart in DB with store_id, currency, version=1, status=active | happy path |
| adds a line item to the cart | active product with variant priced at 2500; add to cart qty 2 | cart_line created, unit_price=2500, subtotal=5000, total=5000 | happy path |
| increments quantity when adding an existing variant | cart already has variant A qty 1; add variant A qty 2 | single line with qty 3, subtotal=7500 | happy path |
| rejects add when product is not active | product status = draft; attempt to add variant to cart | exception thrown | validation |
| rejects add when inventory is insufficient and policy is deny | variant on_hand=2, policy=deny; attempt to add qty 5 | InsufficientInventoryException | validation |
| allows add when inventory is insufficient but policy is continue | variant on_hand=2, policy=continue; add qty 5 | line created with qty 5 | edge case |
| updates line quantity | cart with variant A qty 2; update to qty 5 | line quantity=5, amounts recalculated | happy path |
| removes a line when quantity set to zero | update line quantity to 0 | line removed from cart | edge case |
| removes a specific line item | cart with 2 lines, remove first | 1 line remains | happy path |
| increments cart version on every mutation | create cart (v1), add line (v2), update qty (v3), remove line (v4) | cart_version = 4 | happy path |
| returns cart via session for guest users | create cart, store ID in session; get cart for session | same cart returned | happy path |
| merges guest cart into customer cart on login | guest cart: variant A qty 2; customer cart: variant A qty 1, variant B qty 3; merge | customer cart has A qty 3, B qty 3; guest cart status=abandoned | happy path |

---

#### tests/Feature/Cart/CartApiTest.php

| Test Name | Setup / Preconditions | Key Assertions | Category |
|-----------|----------------------|----------------|----------|
| creates a cart via API | POST /api/storefront/v1/carts | 201 response, response contains cart data | happy path |
| retrieves a cart via API | create cart, then GET /api/storefront/v1/carts/{id} | 200 response, cart data with lines and totals | happy path |
| adds a line via API | POST /api/storefront/v1/carts/{id}/lines with variant_id and quantity | 200 response, line added to cart | happy path |
| updates line quantity via API | PUT /api/storefront/v1/carts/{id}/lines/{lineId} with quantity | 200 response, quantity updated | happy path |
| removes a line via API | DELETE /api/storefront/v1/carts/{id}/lines/{lineId} | 200 response, line removed | happy path |
| returns 404 for nonexistent cart | GET /api/storefront/v1/carts/nonexistent | 404 response | validation |
| returns 409 on version mismatch | cart at version 3; PUT with expected_version=2 | 409 Conflict with current cart state | validation |
| respects storefront rate limiting | make 121 requests in 1 minute | 121st returns 429 | validation |

---

#### tests/Feature/Checkout/CheckoutFlowTest.php

| Test Name | Setup / Preconditions | Key Assertions | Category |
|-----------|----------------------|----------------|----------|
| creates a checkout from a cart | cart with 2 lines; create checkout | checkout in DB, status=started, linked to cart | happy path |
| completes full checkout happy path | create cart -> add lines -> create checkout -> set address -> select shipping -> begin payment -> complete | order created, cart status=converted, inventory committed | happy path |
| rejects checkout for empty cart | cart with 0 lines; create checkout | validation error | validation |
| expires checkout after timeout | checkout with payment_selected status, expires_at in the past; run ExpireAbandonedCheckouts job | status=expired, inventory released | edge case |
| prevents duplicate orders from same checkout | complete checkout once (creates order); call completeCheckout again | no duplicate order created, same order returned | edge case |

---

#### tests/Feature/Checkout/CheckoutStateTest.php

| Test Name | Setup / Preconditions | Key Assertions | Category |
|-----------|----------------------|----------------|----------|
| transitions from started to addressed with valid address | set email and shipping address | status = addressed | happy path |
| rejects address transition with missing required fields | set address without city | validation error, status remains started | validation |
| transitions from addressed to shipping_selected | set valid shipping rate | status = shipping_selected | happy path |
| rejects shipping selection with rate from wrong zone | address in DE, select rate from US-only zone | exception thrown | validation |
| skips shipping selection when no items require shipping | all variants have requires_shipping=false | transitions directly to shipping_selected with shipping=0 | edge case |
| transitions from shipping_selected to payment_selected | select payment method | status = payment_selected, expires_at set, inventory reserved | happy path |
| transitions from payment_selected to completed | simulate successful payment | status = completed, order created | happy path |
| rejects invalid state transitions | attempt started -> completed | exception thrown | validation |
| recalculates pricing on address change | set address (DE), note tax calculation; change address to different country | pricing recalculated | happy path |

---


Output:
Chunk ID: fe9268
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 670
Output:

---

#### tests/Feature/Api/StorefrontCartApiTest.php

| Test Name | Setup / Preconditions | Key Assertions | Category |
|-----------|----------------------|----------------|----------|
| creates a cart | POST /api/storefront/v1/carts | 201 response, body has cart id, version 1 | happy path |
| retrieves a cart with lines and totals | create cart with 2 lines; GET /api/storefront/v1/carts/{id} | 200 response, lines array has 2 items, totals calculated | happy path |
| adds a line to the cart | POST /api/storefront/v1/carts/{id}/lines | 200 response, line added | happy path |
| updates a line quantity | PUT /api/storefront/v1/carts/{id}/lines/{lineId} | 200 response, quantity updated | happy path |
| removes a line | DELETE /api/storefront/v1/carts/{id}/lines/{lineId} | 200 response, line removed | happy path |
| validates variant exists on add | add nonexistent variant_id | 422 response | validation |
| validates quantity is positive | add with quantity=0 | 422 response | validation |
| returns 404 for nonexistent cart | GET /api/storefront/v1/carts/999 | 404 response | validation |

---

#### tests/Feature/Api/StorefrontCheckoutApiTest.php

| Test Name | Setup / Preconditions | Key Assertions | Category |
|-----------|----------------------|----------------|----------|
| creates a checkout from a cart | POST /api/storefront/v1/checkouts with cart_id | 201 response, checkout status=started | happy path |
| sets checkout address | PUT /api/storefront/v1/checkouts/{id}/address | 200 response, status=addressed | happy path |
| selects a shipping method | PUT /api/storefront/v1/checkouts/{id}/shipping-method | 200 response, status=shipping_selected, available rates returned in response | happy path |
| applies a discount code | POST /api/storefront/v1/checkouts/{id}/apply-discount with code | 200 response, discount applied in totals | happy path |
| retrieves checkout with totals | GET /api/storefront/v1/checkouts/{id} | 200 response, includes totals_json fields | happy path |
| selects a payment method | PUT /api/storefront/v1/checkouts/{id}/payment-method with method=credit_card | 200 response, status=payment_selected, payment_method stored | happy path |
| completes checkout with credit card payment | POST /api/storefront/v1/checkouts/{id}/pay with card fields | 200 response, status=completed, order created | happy path |
| rejects payment with declined card | POST /api/storefront/v1/checkouts/{id}/pay with magic decline number | 422 response, payment declined message | validation |
| validates required address fields | PUT address with missing city | 422 response | validation |

---

#### tests/Feature/Api/AdminProductApiTest.php


events dispatch listen On attribute refresh component

livewire nested components event refresh


# Search Results

## Attribute On Alternative: Listening in the template
Attribute On | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/attribute-on

## Alternative: Listening in the template

Instead of using the attribute, you can listen for events directly on child components in your Blade template:

```blade
<livewire:post.edit @saved="$refresh" />
```

This listens for the `saved` event from the `post.edit` child component and refreshes the parent when it's dispatched.

You can also call specific methods:

```blade
<livewire:post.edit @saved="handleSave($event.id)" />
```

---

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

## See also

- **[Navigate](/docs/4.x/navigate)** — Use SPA navigation for redirects
- **[Actions](/docs/4.x/actions)** — Redirect after action completion
- **[Forms](/docs/4.x/forms)** — Redirect after successful form submission
- **[Pages](/docs/4.x/pages)** — Navigate between page components

---

## Javascript Reference
Javascript | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/javascript

## Reference

When extending Livewire's JavaScript system, it's important to understand the different objects you might encounter.

Here is an exhaustive reference of each of Livewire's relevant internal properties.

As a reminder, the average Livewire user may never interact with these. Most of these objects are available for Livewire's internal system or advanced users.

### The `$wire` object

Given the following generic `Counter` component:

```php
<?php

namespace App\Livewire;

use Livewire\Component;

class Counter extends Component
{
    public $count = 1;

    public function increment()
    {
        $this->count++;
    }

    public function render()
    {
        return view('livewire.counter');
    }
}
```

Livewire exposes a JavaScript representation of the server-side component in the form of an object that is commonly referred to as `$wire`:

```js
let $wire = {
    // All component public properties are directly accessible on $wire...
    count: 0,

    // All public methods are exposed and callable on $wire...
    increment() { ... },

    // Access the `$wire` object of the parent component if one exists...
    $parent,

    // Access the root DOM element of the Livewire component...
    $el,

    // Access the ID of the current Livewire component...
    $id,

    // Get the value of a property by name...
    // Usage: $wire.$get('count')
    $get(name) { ... },

    // Set a property on the component by name...
    // Usage: $wire.$set('count', 5)
    $set(name, value, live = true) { ... },

    // Toggle the value of a boolean property...
    $toggle(name, live = true) { ... },

    // Call the method...
    // Usage: $wire.$call('increment')
    $call(method, ...params) { ... },

    // Define a JavaScript action...
    // Usage: $wire.$js('increment', () => { ... })
    // Usage: $wire.$js.increment = () => { ... }
    $js(name, callback) { ... },

    // [DEPRECATED] Entangle - You probably don't need this.
    // Use $wire directly to access properties instead.
    // Usage: <div x-data="{ count: $wire.$entangle('count') }">
    $entangle(name, live = false) { ... },

    // Watch the value of a property for changes...
    // Usage: Alpine.$watch('count', (value, old) => { ... })
    $watch(name, callback) { ... },

    // Scope the next action to a named island...
    // Returns $wire so you can chain any method call.
    // The chained action will only re-render the named island.
    // Usage: $wire.$island('revenue').$refresh()
    // Usage: $wire.$island('feed', { mode: 'append' }).loadMore()
    $island(name, options = {}) { ... },

    // Refresh a component by sending a message to the server
    // to re-render the HTML and swap it into the page...
    $refresh() { ... },

    // Identical to the above `$refresh`. Just a more technical name...
    $commit() { ... }, // Alias for $refresh()

    // Listen for a an event dispatched from this component or its children...
    // Usage: $wire.$on('post-created', () => { ... })
    $on(event, callback) { ... },

    // Listen for a lifecycle hook triggered from this component or the request...
    // Usage: $wire.$hook('message.sent', () => { ... })
    $hook(name, callback) { ... },

    // Dispatch an event from this component...
    // Usage: $wire.$dispatch('post-created', { postId: 2 })
    $dispatch(event, params = {}) { ... },

    // Dispatch an event onto another component...
    // Usage: $wire.$dispatchTo('dashboard', 'post-created', { postId: 2 })
    $dispatchTo(otherComponentName, event, params = {}) { ... },

    // Dispatch an event onto this component and no others...
    $dispatchSelf(event, params = {}) { ... },

    // A JS API to upload a file directly to component
    // rather than through `wire:model`...
    $upload(
        name, // The property name
        file, // The File JavaScript object
        finish = () => { ... }, // Runs when the upload is finished...
        error = () => { ... }, // Runs if an error is triggered mid-upload...
        progress = (event) => { // Runs as the upload progresses...
            event.detail.progress // An integer from 1-100...
        },
    ) { ... },

    // API to upload multiple files at the same time...
    $uploadMultiple(name, files, finish, error, progress) { },

    // Remove an upload after it's been temporarily uploaded but not saved...
    $removeUpload(name, tmpFilename, finish, error) { ... },

    // Register an action interceptor for this component instance
    // Usage: $wire.intercept(({ action, onSend, onCancel, onSuccess, onError, onFailure, onFinish }) => { ... })
    // Or scope to specific action: $wire.intercept('save', ({ action, onSuccess }) => { ... })
    intercept(actionOrCallback, callback) { ... },

    // Alias for intercept
    interceptAction(actionOrCallback, callback) { ... },

    // Register a message interceptor for this component instance
    // Usage: $wire.interceptMessage(({ message, cancel, onSend, onCancel, onSuccess, onSkipped, onError, onFailure, onFinish }) => { ... })
    // Or scope to specific action: $wire.interceptMessage('save', callback)
    interceptMessage(actionOrCallback, callback) { ... },

    // Register a request interceptor for this component instance
    // Usage: $wire.interceptRequest(({ request, onSend, onCancel, onSuccess, onError, onFailure, onFinish }) => { ... })
    // Or scope to specific action: $wire.interceptRequest('save', callback)
    interceptRequest(actionOrCallback, callback) { ... },

    // Retrieve the underlying "component" object...
    __instance() { ... },
}
```

You can learn more about `$wire` in [Livewire's documentation on accessing properties in JavaScript](/docs/4.x/properties#accessing-properties-from-javascript).

### The `snapshot` object

Between each network request, Livewire serializes the PHP component into an object that can be consumed in JavaScript. This snapshot is used to unserialize the component back into a PHP object and therefore has mechanisms built in to prevent tampering:

```js
let snapshot = {
    // The serialized state of the component (public properties)...
    data: { count: 0 },

    // Long-standing information about the component...
    memo: {
        // The component's unique ID...
        id: '0qCY3ri9pzSSMIXPGg8F',

        // The component's name. Ex. <livewire:[name] />
        name: 'counter',

        // The URI, method, and locale of the web page that the
        // component was originally loaded on. This is used
        // to re-apply any middleware from the original request
        // to subsequent component update requests (messages)...
        path: '/',
        method: 'GET',
        locale: 'en',

        // A list of any nested "child" components. Keyed by
        // internal template ID with the component ID as the values...
        children: [],

        // Whether or not this component was "lazy loaded"...
        lazyLoaded: false,

        // A list of any validation errors thrown during the
        // last request...
        errors: [],
    },

    // A securely encrypted hash of this snapshot. This way,
    // if a malicious user tampers with the snapshot with
    // the goal of accessing un-owned resources on the server,
    // the checksum validation will fail and an error will
    // be thrown...
    checksum: '1bc274eea17a434e33d26bcaba4a247a4a7768bd286456a83ea6e9be2d18c1e7',
}
```

### The `component` object

Every component on a page has a corresponding component object behind the scenes keeping track of its state and exposing its underlying functionality. This is one layer deeper than `$wire`. It is only meant for advanced usage.

Here's an actual component object for the above `Counter` component with descriptions of relevant properties in JS comments:

```js
let component = {
    // The root HTML element of the component...
    el: HTMLElement,

    // The unique ID of the component...
    id: '0qCY3ri9pzSSMIXPGg8F',

    // The component's "name" (<livewire:[name] />)...
    name: 'counter',

    // The latest "effects" object. Effects are "side-effects" from server
    // round-trips. These include redirects, file downloads, etc...
    effects: {},

    // The component's last-known server-side state...
    canonical: { count: 0 },

    // The component's mutable data object representing its
    // live client-side state...
    ephemeral: { count: 0 },

    // A reactive version of `this.ephemeral`. Changes to
    // this object will be picked up by AlpineJS expressions...
    reactive: Proxy,

    // A Proxy object that is typically used inside Alpine
    // expressions as `$wire`. This is meant to provide a
    // friendly JS object interface for Livewire components...
    $wire: Proxy,

    // A list of any nested "child" components. Keyed by
    // internal template ID with the component ID as the values...
    children: [],

    // The last-known "snapshot" representation of this component.
    // Snapshots are taken from the server-side component and used
    // to re-create the PHP object on the backend...
    snapshot: {...},

    // The un-parsed version of the above snapshot. This is used to send back to the
    // server on the next roundtrip because JS parsing messes with PHP encoding
    // which often results in checksum mis-matches.
    snapshotEncoded: '{"data":{"count":0},"memo":{"id":"0qCY3ri9pzSSMIXPGg8F","name":"counter","path":"\/","method":"GET","children":[],"lazyLoaded":true,"errors":[],"locale":"en"},"checksum":"1bc274eea17a434e33d26bcaba4a247a4a7768bd286456a83ea6e9be2d18c1e7"}',
}
```

### The `message` payload

When an action is performed on a Livewire component in the browser, a network request is triggered. That network request contains one or many components and various instructions for the server. Internally, these component network payloads are called "messages".

A "message" represents the data sent from the frontend to the backend when a component needs to update. A component is rendered and manipulated on the frontend until an action is performed that requires it to send a message with its state and updates to the backend.

You will recognize this schema from the payload in the network tab of your browser's DevTools, or [Livewire's JavaScript hooks](#javascript-hooks):

```js
let message = {
    // Snapshot object...
    snapshot: { ... },

    // A key-value pair list of properties
    // to update on the server...
    updates: {},

    // An array of methods (with parameters) to call server-side...
    calls: [
        { method: 'increment', params: [] },
    ],
}
```

---

## Actions Refreshing a component
Actions | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/actions

## Refreshing a component

Sometimes you may want to trigger a simple "refresh" of your component. For example, if you have a component checking the status of something in the database, you may want to show a button to your users allowing them to refresh the displayed results.

You can do this using Livewire's simple `$refresh` action anywhere you would normally reference your own component method:

```blade
<button type="button" wire:click="$refresh">...</button>
```

When the `$refresh` action is triggered, Livewire will make a server-roundtrip and re-render your component without calling any methods.

It's important to note that any pending data updates in your component (for example `wire:model` bindings) will be applied on the server when the component is refreshed.

You can also trigger a component refresh using AlpineJS in your Livewire component:

```blade
<button type="button" x-on:click="$wire.$refresh()">...</button>
```

Learn more by reading the [documentation for using Alpine inside Livewire](/docs/4.x/alpine).

---

## Events Listening for events
Events | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/events

## Listening for events

To listen for an event in a Livewire component, add the `#[On]` attribute above the method you want to be called when a given event is dispatched:

> [!warning] Make sure you import attribute classes
> Make sure you import any attribute classes. For example, the below `#[On()]` attributes requires the following import `use Livewire\Attributes\On;`.

```php
<?php // resources/views/components/⚡dashboard.blade.php

use Livewire\Component;
use Livewire\Attributes\On; // [tl! highlight]

new class extends Component {
	#[On('post-created')] // [tl! highlight]
    public function updatePostList($title)
    {
		// ...
    }
};
```

Now, when the `post-created` event is dispatched from `post.create`, a network request will be triggered and the `updatePostList()` action will be invoked.

As you can see, additional data sent with the event will be provided to the action as its first argument.

### Listening for dynamic event names

Occasionally, you may want to dynamically generate event listener names at run-time using data from your component.

For example, if you wanted to scope an event listener to a specific Eloquent model, you could append the model's ID to the event name when dispatching like so:

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

use Livewire\Component;

new class extends Component {
    public function update()
    {
        // ...

        $this->dispatch("post-updated.{$post->id}"); // [tl! highlight]
    }
};
```

And then listen for that specific model:

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

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

new class extends Component {
    public Post $post;

	#[On('post-updated.{post.id}')] // [tl! highlight]
    public function refreshPost()
    {
		// ...
    }
};
```

If the above `$post` model had an ID of `3`, the `refreshPost()` method would only be triggered by an event named: `post-updated.3`.

### Listening for events from specific child components

Livewire allows you to listen for events directly on individual child components in your Blade template like so:

```blade
<div>
    <livewire:edit-post @saved="$refresh">

    <!-- ... -->
</div>
```

In the above scenario, if the `edit-post` child component dispatches a `saved` event, the parent's `$refresh` will be called and the parent will be refreshed.

Instead of passing `$refresh`, you can pass any method you normally would to something like `wire:click`. Here's an example of calling a `close()` method that might do something like close a modal dialog:

```blade
<livewire:edit-post @saved="close">
```

If the child dispatched parameters along with the request, for example `$this->dispatch('saved', postId: 1)`, you can forward those values to the parent method using the following syntax:

```blade
<livewire:edit-post @saved="close($event.detail.postId)">
```

---

## Redirecting Redirect to controller actions
Redirecting | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/redirecting

## Redirect to controller actions

If you want to redirect to a route handled by a controller action, you can use `redirectAction()`:

```php
$this->redirectAction([UserController::class, 'index']);
```

You can pass parameters to the controller action as the second argument:

```php
$this->redirectAction([UserController::class, 'show'], ['id' => 1]);
```

---

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

## See also

- **[Pages](/docs/4.x/pages)** — Create routable page components
- **[Redirecting](/docs/4.x/redirecting)** — Navigate programmatically from actions
- **[@persist](/docs/4.x/directive-persist)** — Persist elements across page navigations
- **[wire:navigate](/docs/4.x/wire-navigate)** — Add SPA navigation to links

---

## Redirecting Flash messages
Redirecting | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/redirecting

## Flash messages

In addition to allowing you to use Laravel's built-in redirection methods, Livewire also supports Laravel's [session flash data utilities](https://laravel.com/docs/session#flash-data).

To pass flash data along with a redirect, you can use Laravel's `session()->flash()` method like so:

```php
<?php

use Livewire\Component;

new class extends Component {
    // ...

    public function update()
    {
        // ...

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

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

Assuming the page being redirected to contain the following Blade snippet, the user will see a "Post successfully updated." message after updating the post:

```blade
@if (session('status'))
    <div class="alert alert-success">
        {{ session('status') }}
    </div>
@endif
```

---

## Wire Ref Dispatching events
Wire Ref | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/wire-ref

## Dispatching events

Refs are a great way to target specific child components within Livewire's event system.

Consider the following Livewire modal component that listens for a _close_ event:

```php
<?php

new class extends Livewire\Component {
    public bool $isOpen = false;

    // ...

    #[On('close')]
    public function close()
    {
        $this->isOpen = false;
    }
};
?>

<div wire:show="isOpen">
    {{ $slot }}
</div>
```

By adding `wire:ref` to the component tag, you now dispatch the _close_ event directly to it using the `ref:` parameter:

```php
<?php

new class extends Livewire\Component {
    public function save()
    {
        //

        $this->dispatch('close')->to(ref: 'modal');
    }
};
?>

<div>
    <!-- ... -->

    <livewire:modal wire:ref="modal">
        <!-- ... -->

        <button wire:click="save">Save</button>
    </livewire:modal>
</div>
```

---

## Wire Model Accessing nested properties
Wire Model | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/wire-model

## Accessing nested properties

`wire:model` supports dot notation for binding to nested properties, array elements, and form object fields:

```blade
<input type="text" wire:model="address.city">

<input type="text" wire:model="items.0.name">

<input type="text" wire:model="form.title">
```

Bracket notation is also supported as an alternative:

```blade
<input type="text" wire:model="address['city']">

<input type="text" wire:model="items[0].name">
```

Both notations are equivalent — `address['city']` resolves the same path as `address.city`. Bracket and dot notation can be mixed freely within a single expression.

---

## Understanding Nesting Every component is independent {#every-component-is-an-island}
Understanding Nesting | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/understanding-nesting

## Every component is independent {#every-component-is-an-island}

In Livewire, every component on a page tracks its state and makes updates independently of other components.

For example, consider the following `Posts` and nested `ShowPost` components:

```php
<?php

namespace App\Livewire;

use Illuminate\Support\Facades\Auth;
use Livewire\Component;

class Posts extends Component
{
    public $postLimit = 2;

    public function render()
    {
        return view('livewire.posts', [
            'posts' => Auth::user()->posts()
                ->limit($this->postLimit)->get(),
        ]);
    }
}
```

```blade
<div>
    Post Limit: <input type="number" wire:model.live="postLimit">

    @foreach ($posts as $post)
        <livewire:show-post :$post :wire:key="$post->id">
    @endforeach
</div>
```

```php
<?php

namespace App\Livewire;

use Illuminate\Support\Facades\Auth;
use Livewire\Component;
use App\Models\Post;

class ShowPost extends Component
{
    public Post $post;

    public function render()
    {
        return view('livewire.show-post');
    }
}
```

```blade
<div>
    <h1>{{ $post->title }}</h1>

    <p>{{ $post->content }}</p>

    <button wire:click="$refresh">Refresh post</button>
</div>
```

Here's what the HTML for the entire component tree might look like on initial page load:

```html
<div wire:id="123" wire:snapshot="...">
    Post Limit: <input type="number" wire:model.live="postLimit">

    <div wire:id="456" wire:snapshot="...">
        <h1>The first post</h1>

        <p>Post content</p>

        <button wire:click="$refresh">Refresh post</button>
    </div>

    <div wire:id="789" wire:snapshot="...">
        <h1>The second post</h1>

        <p>Post content</p>

        <button wire:click="$refresh">Refresh post</button>
    </div>
</div>
```

Notice that the parent component contains both its rendered template and the rendered templates of all the components nested within it.

Because each component is independent, they each have their own IDs and snapshots (`wire:id` and `wire:snapshot`) embedded in their HTML for Livewire's JavaScript core to extract and track.

Let's consider a few different update scenarios to see the differences in how Livewire handles different levels of nesting.

### Updating a child

If you were to click the "Refresh post" button in one of the child `show-post` components, here is what would be sent to the server:

```js
{
    memo: { name: 'show-post', id: '456' },

    state: { ... },
}
```

Here's the HTML that would get sent back:

```html
<div wire:id="456">
    <h1>The first post</h1>

    <p>Post content</p>

    <button wire:click="$refresh">Refresh post</button>
</div>
```

The important thing to note here is that when an update is triggered on a child component, only that component's data is sent to the server, and only that component is re-rendered.

Now let's look at the less intuitive scenario: updating a parent component.

### Updating the parent

As a reminder, here's the Blade template of the parent `Posts` component:

```blade
<div>
    Post Limit: <input type="number" wire:model.live="postLimit">

    @foreach ($posts as $post)
        <livewire:show-post :$post :wire:key="$post->id">
    @endforeach
</div>
```

If a user changes the "Post Limit" value from `2` to `1`, an update will be solely triggered on the parent.

Here's an example of what the request payload might look like:

```js
{
    updates: { postLimit: 1 },

    snapshot: {
        memo: { name: 'posts', id: '123' },

        state: { postLimit: 2, ... },
    },
}
```

As you can see, only the snapshot for the parent `Posts` component is sent along to the server.

An important question you might be asking yourself is: what happens when the parent component re-renders and encounters the child `show-post` components? How will it re-render the children if their snapshots haven't been included in the request payload?

The answer is: they won't be re-rendered.

When Livewire renders the `Posts` component, it will render placeholders for any child components it encounters.

Here is an example of what the rendered HTML for the `Posts` component might be after the above update:

```html
<div wire:id="123">
    Post Limit: <input type="number" wire:model.live="postLimit">

    <div wire:id="456"></div>
</div>
```

As you can see, only one child has been rendered because `postLimit` was updated to `1`. However, you will also notice that instead of the full child component, there is only an empty `<div></div>` with the matching `wire:id` attribute.

When this HTML is received on the frontend, Livewire will _morph_ the old HTML for this component into this new HTML, but intelligently skip any child component placeholders.

The effect is that, after _morphing_, the final DOM content of the parent `Posts` component will be the following:

```html
<div wire:id="123">
    Post Limit: <input type="number" wire:model.live="postLimit">

    <div wire:id="456">
        <h1>The first post</h1>

        <p>Post content</p>

        <button wire:click="$refresh">Refresh post</button>
    </div>
</div>
```

_[Truncated]_


Output:
Wall time: 3.3773 seconds
Output:
[{"type":"text","text":"# Search Results\n\n## Attribute On Alternative: Listening in the template\nAttribute On | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/attribute-on\n\n## Alternative: Listening in the template\n\nInstead of using the attribute, you can listen for events directly on child components in your Blade template:\n\n```blade\n<livewire:post.edit @saved=\"$refresh\" />\n```\n\nThis listens for the `saved` event from the `post.edit` child component and refreshes the parent when it's dispatched.\n\nYou can also call specific methods:\n\n```blade\n<livewire:post.edit @saved=\"handleSave($event.id)\" />\n```\n\n---\n\n## Redirecting See also\nRedirecting | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/redirecting\n\n## See also\n\n- **[Navigate](/docs/4.x/navigate)** — Use SPA navigation for redirects\n- **[Actions](/docs/4.x/actions)** — Redirect after action completion\n- **[Forms](/docs/4.x/forms)** — Redirect after successful form submission\n- **[Pages](/docs/4.x/pages)** — Navigate between page components\n\n---\n\n## Javascript Reference\nJavascript | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/javascript\n\n## Reference\n\nWhen extending Livewire's JavaScript system, it's important to understand the different objects you might encounter.\n\nHere is an exhaustive reference of each of Livewire's relevant internal properties.\n\nAs a reminder, the average Livewire user may never interact with these. Most of these objects are available for Livewire's internal system or advanced users.\n\n### The `$wire` object\n\nGiven the following generic `Counter` component:\n\n```php\n<?php\n\nnamespace App\\Livewire;\n\nuse Livewire\\Component;\n\nclass Counter extends Component\n{\n    public $count = 1;\n\n    public function increment()\n    {\n        $this->count++;\n    }\n\n    public function render()\n    {\n        return view('livewire.counter');\n    }\n}\n```\n\nLivewire exposes a JavaScript representation of the server-side component in the form of an object that is commonly referred to as `$wire`:\n\n```js\nlet $wire = {\n    // All component public properties are directly accessible on $wire...\n    count: 0,\n\n    // All public methods are exposed and callable on $wire...\n    increment() { ... },\n\n    // Access the `$wire` object of the parent component if one exists...\n    $parent,\n\n    // Access the root DOM element of the Livewire component...\n    $el,\n\n    // Access the ID of the current Livewire component...\n    $id,\n\n    // Get the value of a property by name...\n    // Usage: $wire.$get('count')\n    $get(name) { ... },\n\n    // Set a property on the component by name...\n    // Usage: $wire.$set('count', 5)\n    $set(name, value, live = true) { ... },\n\n    // Toggle the value of a boolean property...\n    $toggle(name, live = true) { ... },\n\n    // Call the method...\n    // Usage: $wire.$call('increment')\n    $call(method, ...params) { ... },\n\n    // Define a JavaScript action...\n    // Usage: $wire.$js('increment', () => { ... })\n    // Usage: $wire.$js.increment = () => { ... }\n    $js(name, callback) { ... },\n\n    // [DEPRECATED] Entangle - You probably don't need this.\n    // Use $wire directly to access properties instead.\n    // Usage: <div x-data=\"{ count: $wire.$entangle('count') }\">\n    $entangle(name, live = false) { ... },\n\n    // Watch the value of a property for changes...\n    // Usage: Alpine.$watch('count', (value, old) => { ... })\n    $watch(name, callback) { ... },\n\n    // Scope the next action to a named island...\n    // Returns $wire so you can chain any method call.\n    // The chained action will only re-render the named island.\n    // Usage: $wire.$island('revenue').$refresh()\n    // Usage: $wire.$island('feed', { mode: 'append' }).loadMore()\n    $island(name, options = {}) { ... },\n\n    // Refresh a component by sending a message to the server\n    // to re-render the HTML and swap it into the page...\n    $refresh() { ... },\n\n    // Identical to the above `$refresh`. Just a more technical name...\n    $commit() { ... }, // Alias for $refresh()\n\n    // Listen for a an event dispatched from this component or its children...\n    // Usage: $wire.$on('post-created', () => { ... })\n    $on(event, callback) { ... },\n\n    // Listen for a lifecycle hook triggered from this component or the request...\n    // Usage: $wire.$hook('message.sent', () => { ... })\n    $hook(name, callback) { ... },\n\n    // Dispatch an event from this component...\n    // Usage: $wire.$dispatch('post-created', { postId: 2 })\n    $dispatch(event, params = {}) { ... },\n\n    // Dispatch an event onto another component...\n    // Usage: $wire.$dispatchTo('dashboard', 'post-created', { postId: 2 })\n    $dispatchTo(otherComponentName, event, params = {}) { ... },\n\n    // Dispatch an event onto this component and no others...\n    $dispatchSelf(event, params = {}) { ... },\n\n    // A JS API to upload a file directly to component\n    // rather than through `wire:model`...\n    $upload(\n        name, // The property name\n        file, // The File JavaScript object\n        finish = () => { ... }, // Runs when the upload is finished...\n        error = () => { ... }, // Runs if an error is triggered mid-upload...\n        progress = (event) => { // Runs as the upload progresses...\n            event.detail.progress // An integer from 1-100...\n        },\n    ) { ... },\n\n    // API to upload multiple files at the same time...\n    $uploadMultiple(name, files, finish, error, progress) { },\n\n    // Remove an upload after it's been temporarily uploaded but not saved...\n    $removeUpload(name, tmpFilename, finish, error) { ... },\n\n    // Register an action interceptor for this component instance\n    // Usage: $wire.intercept(({ action, onSend, onCancel, onSuccess, onError, onFailure, onFinish }) => { ... })\n    // Or scope to specific action: $wire.intercept('save', ({ action, onSuccess }) => { ... })\n    intercept(actionOrCallback, callback) { ... },\n\n    // Alias for intercept\n    interceptAction(actionOrCallback, callback) { ... },\n\n    // Register a message interceptor for this component instance\n    // Usage: $wire.interceptMessage(({ message, cancel, onSend, onCancel, onSuccess, onSkipped, onError, onFailure, onFinish }) => { ... })\n    // Or scope to specific action: $wire.interceptMessage('save', callback)\n    interceptMessage(actionOrCallback, callback) { ... },\n\n    // Register a request interceptor for this component instance\n    // Usage: $wire.interceptRequest(({ request, onSend, onCancel, onSuccess, onError, onFailure, onFinish }) => { ... })\n    // Or scope to specific action: $wire.interceptRequest('save', callback)\n    interceptRequest(actionOrCallback, callback) { ... },\n\n    // Retrieve the underlying \"component\" object...\n    __instance() { ... },\n}\n```\n\nYou can learn more about `$wire` in [Livewire's documentation on accessing properties in JavaScript](/docs/4.x/properties#accessing-properties-from-javascript).\n\n### The `snapshot` object\n\nBetween each network request, Livewire serializes the PHP component into an object that can be consumed in JavaScript. This snapshot is used to unserialize the component back into a PHP object and therefore has mechanisms built in to prevent tampering:\n\n```js\nlet snapshot = {\n    // The serialized state of the component (public properties)...\n    data: { count: 0 },\n\n    // Long-standing information about the component...\n    memo: {\n        // The component's unique ID...\n        id: '0qCY3ri9pzSSMIXPGg8F',\n\n        // The component's name. Ex. <livewire:[name] />\n        name: 'counter',\n\n        // The URI, method, and locale of the web page that the\n        // component was originally loaded on. This is used\n        // to re-apply any middleware from the original request\n        // to subsequent component update requests (messages)...\n        path: '/',\n        method: 'GET',\n        locale: 'en',\n\n        // A list of any nested \"child\" components. Keyed by\n        // internal template ID with the component ID as the values...\n        children: [],\n\n        // Whether or not this component was \"lazy loaded\"...\n        lazyLoaded: false,\n\n        // A list of any validation errors thrown during the\n        // last request...\n        errors: [],\n    },\n\n    // A securely encrypted hash of this snapshot. This way,\n    // if a malicious user tampers with the snapshot with\n    // the goal of accessing un-owned resources on the server,\n    // the checksum validation will fail and an error will\n    // be thrown...\n    checksum: '1bc274eea17a434e33d26bcaba4a247a4a7768bd286456a83ea6e9be2d18c1e7',\n}\n```\n\n### The `component` object\n\nEvery component on a page has a corresponding component object behind the scenes keeping track of its state and exposing its underlying functionality. This is one layer deeper than `$wire`. It is only meant for advanced usage.\n\nHere's an actual component object for the above `Counter` component with descriptions of relevant properties in JS comments:\n\n```js\nlet component = {\n    // The root HTML element of the component...\n    el: HTMLElement,\n\n    // The unique ID of the component...\n    id: '0qCY3ri9pzSSMIXPGg8F',\n\n    // The component's \"name\" (<livewire:[name] />)...\n    name: 'counter',\n\n    // The latest \"effects\" object. Effects are \"side-effects\" from server\n    // round-trips. These include redirects, file downloads, etc...\n    effects: {},\n\n    // The component's last-known server-side state...\n    canonical: { count: 0 },\n\n    // The component's mutable data object representing its\n    // live client-side state...\n    ephemeral: { count: 0 },\n\n    // A reactive version of `this.ephemeral`. Changes to\n    // this object will be picked up by AlpineJS expressions...\n    reactive: Proxy,\n\n    // A Proxy object that is typically used inside Alpine\n    // expressions as `$wire`. This is meant to provide a\n    // friendly JS object interface for Livewire components...\n    $wire: Proxy,\n\n    // A list of any nested \"child\" components. Keyed by\n    // internal template ID with the component ID as the values...\n    children: [],\n\n    // The last-known \"snapshot\" representation of this component.\n    // Snapshots are taken from the server-side component and used\n    // to re-create the PHP object on the backend...\n    snapshot: {...},\n\n    // The un-parsed version of the above snapshot. This is used to send back to the\n    // server on the next roundtrip because JS parsing messes with PHP encoding\n    // which often results in checksum mis-matches.\n    snapshotEncoded: '{\"data\":{\"count\":0},\"memo\":{\"id\":\"0qCY3ri9pzSSMIXPGg8F\",\"name\":\"counter\",\"path\":\"\\/\",\"method\":\"GET\",\"children\":[],\"lazyLoaded\":true,\"errors\":[],\"locale\":\"en\"},\"checksum\":\"1bc274eea17a434e33d26bcaba4a247a4a7768bd286456a83ea6e9be2d18c1e7\"}',\n}\n```\n\n### The `message` payload\n\nWhen an action is performed on a Livewire component in the browser, a network request is triggered. That network request contains one or many components and various instructions for the server. Internally, these component network payloads are called \"messages\".\n\nA \"message\" represents the data sent from the frontend to the backend when a component needs to update. A component is rendered and manipulated on the frontend until an action is performed that requires it to send a message with its state and updates to the backend.\n\nYou will recognize this schema from the payload in the network tab of your browser's DevTools, or [Livewire's JavaScript hooks](#javascript-hooks):\n\n```js\nlet message = {\n    // Snapshot object...\n    snapshot: { ... },\n\n    // A key-value pair list of properties\n    // to update on the server...\n    updates: {},\n\n    // An array of methods (with parameters) to call server-side...\n    calls: [\n        { method: 'increment', params: [] },\n    ],\n}\n```\n\n---\n\n## Actions Refreshing a component\nActions | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/actions\n\n## Refreshing a component\n\nSometimes you may want to trigger a simple \"refresh\" of your component. For example, if you have a component checking the status of something in the database, you may want to show a button to your users allowing them to refresh the displayed results.\n\nYou can do this using Livewire's simple `$refresh` action anywhere you would normally reference your own component method:\n\n```blade\n<button type=\"button\" wire:click=\"$refresh\">...</button>\n```\n\nWhen the `$refresh` action is triggered, Livewire will make a server-roundtrip and re-render your component without calling any methods.\n\nIt's important to note that any pending data updates in your component (for example `wire:model` bindings) will be applied on the server when the component is refreshed.\n\nYou can also trigger a component refresh using AlpineJS in your Livewire component:\n\n```blade\n<button type=\"button\" x-on:click=\"$wire.$refresh()\">...</button>\n```\n\nLearn more by reading the [documentation for using Alpine inside Livewire](/docs/4.x/alpine).\n\n---\n\n## Events Listening for events\nEvents | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/events\n\n## Listening for events\n\nTo listen for an event in a Livewire component, add the `#[On]` attribute above the method you want to be called when a given event is dispatched:\n\n> [!warning] Make sure you import attribute classes\n> Make sure you import any attribute classes. For example, the below `#[On()]` attributes requires the following import `use Livewire\\Attributes\\On;`.\n\n```php\n<?php // resources/views/components/⚡dashboard.blade.php\n\nuse Livewire\\Component;\nuse Livewire\\Attributes\\On; // [tl! highlight]\n\nnew class extends Component {\n\t#[On('post-created')] // [tl! highlight]\n    public function updatePostList($title)\n    {\n\t\t// ...\n    }\n};\n```\n\nNow, when the `post-created` event is dispatched from `post.create`, a network request will be triggered and the `updatePostList()` action will be invoked.\n\nAs you can see, additional data sent with the event will be provided to the action as its first argument.\n\n### Listening for dynamic event names\n\nOccasionally, you may want to dynamically generate event listener names at run-time using data from your component.\n\nFor example, if you wanted to scope an event listener to a specific Eloquent model, you could append the model's ID to the event name when dispatching like so:\n\n```php\n<?php // resources/views/components/post/⚡edit.blade.php\n\nuse Livewire\\Component;\n\nnew class extends Component {\n    public function update()\n    {\n        // ...\n\n        $this->dispatch(\"post-updated.{$post->id}\"); // [tl! highlight]\n    }\n};\n```\n\nAnd then listen for that specific model:\n\n```php\n<?php // resources/views/components/post/⚡show.blade.php\n\nuse Livewire\\Attributes\\On; // [tl! highlight]\nuse Livewire\\Component;\nuse App\\Models\\Post;\n\nnew class extends Component {\n    public Post $post;\n\n\t#[On('post-updated.{post.id}')] // [tl! highlight]\n    public function refreshPost()\n    {\n\t\t// ...\n    }\n};\n```\n\nIf the above `$post` model had an ID of `3`, the `refreshPost()` method would only be triggered by an event named: `post-updated.3`.\n\n### Listening for events from specific child components\n\nLivewire allows you to listen for events directly on individual child components in your Blade template like so:\n\n```blade\n<div>\n    <livewire:edit-post @saved=\"$refresh\">\n\n    <!-- ... -->\n</div>\n```\n\nIn the above scenario, if the `edit-post` child component dispatches a `saved` event, the parent's `$refresh` will be called and the parent will be refreshed.\n\nInstead of passing `$refresh`, you can pass any method you normally would to something like `wire:click`. Here's an example of calling a `close()` method that might do something like close a modal dialog:\n\n```blade\n<livewire:edit-post @saved=\"close\">\n```\n\nIf the child dispatched parameters along with the request, for example `$this->dispatch('saved', postId: 1)`, you can forward those values to the parent method using the following syntax:\n\n```blade\n<livewire:edit-post @saved=\"close($event.detail.postId)\">\n```\n\n---\n\n## Redirecting Redirect to controller actions\nRedirecting | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/redirecting\n\n## Redirect to controller actions\n\nIf you want to redirect to a route handled by a controller action, you can use `redirectAction()`:\n\n```php\n$this->redirectAction([UserController::class, 'index']);\n```\n\nYou can pass parameters to the controller action as the second argument:\n\n```php\n$this->redirectAction([UserController::class, 'show'], ['id' => 1]);\n```\n\n---\n\n## Navigate See also\nNavigate | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/navigate\n\n## See also\n\n- **[Pages](/docs/4.x/pages)** — Create routable page components\n- **[Redirecting](/docs/4.x/redirecting)** — Navigate programmatically from actions\n- **[@persist](/docs/4.x/directive-persist)** — Persist elements across page navigations\n- **[wire:navigate](/docs/4.x/wire-navigate)** — Add SPA navigation to links\n\n---\n\n## Redirecting Flash messages\nRedirecting | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/redirecting\n\n## Flash messages\n\nIn addition to allowing you to use Laravel's built-in redirection methods, Livewire also supports Laravel's [session flash data utilities](https://laravel.com/docs/session#flash-data).\n\nTo pass flash data along with a redirect, you can use Laravel's `session()->flash()` method like so:\n\n```php\n<?php\n\nuse Livewire\\Component;\n\nnew class extends Component {\n    // ...\n\n    public function update()\n    {\n        // ...\n\n        session()->flash('status', 'Post successfully updated.');\n\n        $this->redirect('/posts');\n    }\n};\n?>\n```\n\nAssuming the page being redirected to contain the following Blade snippet, the user will see a \"Post successfully updated.\" message after updating the post:\n\n```blade\n@if (session('status'))\n    <div class=\"alert alert-success\">\n        {{ session('status') }}\n    </div>\n@endif\n```\n\n---\n\n## Wire Ref Dispatching events\nWire Ref | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/wire-ref\n\n## Dispatching events\n\nRefs are a great way to target specific child components within Livewire's event system.\n\nConsider the following Livewire modal component that listens for a _close_ event:\n\n```php\n<?php\n\nnew class extends Livewire\\Component {\n    public bool $isOpen = false;\n\n    // ...\n\n    #[On('close')]\n    public function close()\n    {\n        $this->isOpen = false;\n    }\n};\n?>\n\n<div wire:show=\"isOpen\">\n    {{ $slot }}\n</div>\n```\n\nBy adding `wire:ref` to the component tag, you now dispatch the _close_ event directly to it using the `ref:` parameter:\n\n```php\n<?php\n\nnew class extends Livewire\\Component {\n    public function save()\n    {\n        //\n\n        $this->dispatch('close')->to(ref: 'modal');\n    }\n};\n?>\n\n<div>\n    <!-- ... -->\n\n    <livewire:modal wire:ref=\"modal\">\n        <!-- ... -->\n\n        <button wire:click=\"save\">Save</button>\n    </livewire:modal>\n</div>\n```\n\n---\n\n## Wire Model Accessing nested properties\nWire Model | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/wire-model\n\n## Accessing nested properties\n\n`wire:model` supports dot notation for binding to nested properties, array elements, and form object fields:\n\n```blade\n<input type=\"text\" wire:model=\"address.city\">\n\n<input type=\"text\" wire:model=\"items.0.name\">\n\n<input type=\"text\" wire:model=\"form.title\">\n```\n\nBracket notation is also supported as an alternative:\n\n```blade\n<input type=\"text\" wire:model=\"address['city']\">\n\n<input type=\"text\" wire:model=\"items[0].name\">\n```\n\nBoth notations are equivalent — `address['city']` resolves the same path as `address.city`. Bracket and dot notation can be mixed freely within a single expression.\n\n---\n\n## Understanding Nesting Every component is independent {#every-component-is-an-island}\nUnderstanding Nesting | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/understanding-nesting\n\n## Every component is independent {#every-component-is-an-island}\n\nIn Livewire, every component on a page tracks its state and makes updates independently of other components.\n\nFor example, consider the following `Posts` and nested `ShowPost` components:\n\n```php\n<?php\n\nnamespace App\\Livewire;\n\nuse Illuminate\\Support\\Facades\\Auth;\nuse Livewire\\Component;\n\nclass Posts extends Component\n{\n    public $postLimit = 2;\n\n    public function render()\n    {\n        return view('livewire.posts', [\n            'posts' => Auth::user()->posts()\n                ->limit($this->postLimit)->get(),\n        ]);\n    }\n}\n```\n\n```blade\n<div>\n    Post Limit: <input type=\"number\" wire:model.live=\"postLimit\">\n\n    @foreach ($posts as $post)\n        <livewire:show-post :$post :wire:key=\"$post->id\">\n    @endforeach\n</div>\n```\n\n```php\n<?php\n\nnamespace App\\Livewire;\n\nuse Illuminate\\Support\\Facades\\Auth;\nuse Livewire\\Component;\nuse App\\Models\\Post;\n\nclass ShowPost extends Component\n{\n    public Post $post;\n\n    public function render()\n    {\n        return view('livewire.show-post');\n    }\n}\n```\n\n```blade\n<div>\n    <h1>{{ $post->title }}</h1>\n\n    <p>{{ $post->content }}</p>\n\n    <button wire:click=\"$refresh\">Refresh post</button>\n</div>\n```\n\nHere's what the HTML for the entire component tree might look like on initial page load:\n\n```html\n<div wire:id=\"123\" wire:snapshot=\"...\">\n    Post Limit: <input type=\"number\" wire:model.live=\"postLimit\">\n\n    <div wire:id=\"456\" wire:snapshot=\"...\">\n        <h1>The first post</h1>\n\n        <p>Post content</p>\n\n        <button wire:click=\"$refresh\">Refresh post</button>\n    </div>\n\n    <div wire:id=\"789\" wire:snapshot=\"...\">\n        <h1>The second post</h1>\n\n        <p>Post content</p>\n\n        <button wire:click=\"$refresh\">Refresh post</button>\n    </div>\n</div>\n```\n\nNotice that the parent component contains both its rendered template and the rendered templates of all the components nested within it.\n\nBecause each component is independent, they each have their own IDs and snapshots (`wire:id` and `wire:snapshot`) embedded in their HTML for Livewire's JavaScript core to extract and track.\n\nLet's consider a few different update scenarios to see the differences in how Livewire handles different levels of nesting.\n\n### Updating a child\n\nIf you were to click the \"Refresh post\" button in one of the child `show-post` components, here is what would be sent to the server:\n\n```js\n{\n    memo: { name: 'show-post', id: '456' },\n\n    state: { ... },\n}\n```\n\nHere's the HTML that would get sent back:\n\n```html\n<div wire:id=\"456\">\n    <h1>The first post</h1>\n\n    <p>Post content</p>\n\n    <button wire:click=\"$refresh\">Refresh post</button>\n</div>\n```\n\nThe important thing to note here is that when an update is triggered on a child component, only that component's data is sent to the server, and only that component is re-rendered.\n\nNow let's look at the less intuitive scenario: updating a parent component.\n\n### Updating the parent\n\nAs a reminder, here's the Blade template of the parent `Posts` component:\n\n```blade\n<div>\n    Post Limit: <input type=\"number\" wire:model.live=\"postLimit\">\n\n    @foreach ($posts as $post)\n        <livewire:show-post :$post :wire:key=\"$post->id\">\n    @endforeach\n</div>\n```\n\nIf a user changes the \"Post Limit\" value from `2` to `1`, an update will be solely triggered on the parent.\n\nHere's an example of what the request payload might look like:\n\n```js\n{\n    updates: { postLimit: 1 },\n\n    snapshot: {\n        memo: { name: 'posts', id: '123' },\n\n        state: { postLimit: 2, ... },\n    },\n}\n```\n\nAs you can see, only the snapshot for the parent `Posts` component is sent along to the server.\n\nAn important question you might be asking yourself is: what happens when the parent component re-renders and encounters the child `show-post` components? How will it re-render the children if their snapshots haven't been included in the request payload?\n\nThe answer is: they won't be re-rendered.\n\nWhen Livewire renders the `Posts` component, it will render placeholders for any child components it encounters.\n\nHere is an example of what the rendered HTML for the `Posts` component might be after the above update:\n\n```html\n<div wire:id=\"123\">\n    Post Limit: <input type=\"number\" wire:model.live=\"postLimit\">\n\n    <div wire:id=\"456\"></div>\n</div>\n```\n\nAs you can see, only one child has been rendered because `postLimit` was updated to `1`. However, you will also notice that instead of the full child component, there is only an empty `<div></div>` with the matching `wire:id` attribute.\n\nWhen this HTML is received on the frontend, Livewire will _morph_ the old HTML for this component into this new HTML, but intelligently skip any child component placeholders.\n\nThe effect is that, after _morphing_, the final DOM content of the parent `Posts` component will be the following:\n\n```html\n<div wire:id=\"123\">\n    Post Limit: <input type=\"number\" wire:model.live=\"postLimit\">\n\n    <div wire:id=\"456\">\n        <h1>The first post</h1>\n\n        <p>Post content</p>\n\n        <button wire:click=\"$refresh\">Refresh post</button>\n    </div>\n</div>\n```\n\n_[Truncated]_"}]


sed -n '1,180p' resources/views/components/storefront/product-card.blade.php


sed -n '1,180p' resources/views/components/storefront/product-card.blade.php

@props(['product'])

@php
    $variant = $product->variants->first();
    $isSoldOut = $product->variants->isNotEmpty()
        && $product->variants->every(fn ($variant) => $variant->inventoryItem?->policy?->value === 'deny' && $variant->inventoryItem?->availableQuantity() <= 0);
    $isOnSale = $variant?->compare_at_amount && $variant->compare_at_amount > $variant->price_amount;
@endphp

class('group') }}>
    handle) }}" class="block space-y-3" wire:navigate>

                @if ($isOnSale)
                    Sale
                @endif

                @if ($isSoldOut)
                    Sold out
                @endif

                <flux:icon name="shopping-bag" class="size-12" />

{{ $product->title }}

            @if ($variant)
                <x-storefront.price :amount="$variant->price_amount" :compare-at="$variant->compare_at_amount" :currency="$variant->currency" class="text-sm" />
            @endif

                {{ $product->variants_count ?? $product->variants->count() }} {{ Str::plural('variant', $product->variants_count ?? $product->variants->count()) }}


Output:
Chunk ID: 38b5e0
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 511
Output:
@props(['product'])

@php
    $variant = $product->variants->first();
    $isSoldOut = $product->variants->isNotEmpty()
        && $product->variants->every(fn ($variant) => $variant->inventoryItem?->policy?->value === 'deny' && $variant->inventoryItem?->availableQuantity() <= 0);
    $isOnSale = $variant?->compare_at_amount && $variant->compare_at_amount > $variant->price_amount;
@endphp

class('group') }}>
    handle) }}" class="block space-y-3" wire:navigate>

                @if ($isOnSale)
                    Sale
                @endif

                @if ($isSoldOut)
                    Sold out
                @endif

                <flux:icon name="shopping-bag" class="size-12" />

{{ $product->title }}

            @if ($variant)
                <x-storefront.price :amount="$variant->price_amount" :compare-at="$variant->compare_at_amount" :currency="$variant->currency" class="text-sm" />
            @endif

                {{ $product->variants_count ?? $product->variants->count() }} {{ Str::plural('variant', $product->variants_count ?? $product->variants->count()) }}


sed -n '1,120p' resources/views/components/storefront/price.blade.php


sed -n '1,120p' resources/views/components/storefront/price.blade.php

@props([
    'amount',
    'currency' => 'EUR',
    'compareAt' => null,
])

class('inline-flex flex-wrap items-baseline gap-2') }}>
    {{ \App\Support\Money::format((int) $amount, $currency) }}

    @if ($compareAt && $compareAt > $amount)
        {{ \App\Support\Money::format((int) $compareAt, $currency) }}
    @endif


Output:
Chunk ID: e8202b
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 124
Output:
@props([
    'amount',
    'currency' => 'EUR',
    'compareAt' => null,
])

class('inline-flex flex-wrap items-baseline gap-2') }}>
    {{ \App\Support\Money::format((int) $amount, $currency) }}

    @if ($compareAt && $compareAt > $amount)
        {{ \App\Support\Money::format((int) $compareAt, $currency) }}
    @endif


Existing storefront pages are class-based Livewire components with `resources/views/livewire/...` views. I’m creating the cart drawer, cart page, and checkout page in that same format, then I’ll wire product add-to-cart into `CartService`.


php artisan make:livewire Storefront/CartDrawer --class --no-interaction


Output:
Chunk ID: 44a1ec
Wall time: 0.1501 seconds
Process exited with code 0
Original token count: 24
Output:

   INFO  Livewire component [app/Livewire/Storefront/CartDrawer.php] created successfully.


php artisan make:livewire Storefront/CartDrawer --class --no-interaction

INFO  Livewire component [app/Livewire/Storefront/CartDrawer.php] created successfully.


php artisan make:livewire Storefront/Cart/Show --class --no-interaction


Output:
Chunk ID: f64c4a
Wall time: 0.0387 seconds
Process exited with code 0
Original token count: 24
Output:

   INFO  Livewire component [app/Livewire/Storefront/Cart/Show.php] created successfully.


php artisan make:livewire Storefront/Cart/Show --class --no-interaction

INFO  Livewire component [app/Livewire/Storefront/Cart/Show.php] created successfully.


php artisan make:livewire Storefront/Checkout/Show --class --no-interaction


Output:
Chunk ID: 15e3d2
Wall time: 0.0586 seconds
Process exited with code 0
Original token count: 25
Output:

   INFO  Livewire component [app/Livewire/Storefront/Checkout/Show.php] created successfully.


php artisan make:livewire Storefront/Checkout/Show --class --no-interaction

INFO  Livewire component [app/Livewire/Storefront/Checkout/Show.php] created successfully.


sed -n '1,120p' app/Livewire/Storefront/CartDrawer.php


sed -n '1,120p' resources/views/livewire/storefront/cart-drawer.blade.php


sed -n '1,120p' app/Livewire/Storefront/Cart/Show.php


sed -n '1,120p' app/Livewire/Storefront/Checkout/Show.php


sed -n '1,120p' resources/views/livewire/storefront/cart-drawer.blade.php

{{-- Always remember that you are absolutely unique. Just like everyone else. - Margaret Mead --}}


sed -n '1,120p' app/Livewire/Storefront/CartDrawer.php

<?php

namespace App\Livewire\Storefront;

use Livewire\Component;

class CartDrawer extends Component
{
    public function render()
    {
        return view('livewire.storefront.cart-drawer');
    }
}


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

namespace App\Livewire\Storefront;

use Livewire\Component;

class CartDrawer extends Component
{
    public function render()
    {
        return view('livewire.storefront.cart-drawer');
    }
}


Output:
Chunk ID: 6a557b
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 29
Output:
<div>
    {{-- Always remember that you are absolutely unique. Just like everyone else. - Margaret Mead --}}
</div>


sed -n '1,120p' app/Livewire/Storefront/Checkout/Show.php

<?php

namespace App\Livewire\Storefront\Checkout;

use Livewire\Component;

class Show extends Component
{
    public function render()
    {
        return view('livewire.storefront.checkout.show');
    }
}


sed -n '1,120p' app/Livewire/Storefront/Cart/Show.php

<?php

namespace App\Livewire\Storefront\Cart;

use Livewire\Component;

class Show extends Component
{
    public function render()
    {
        return view('livewire.storefront.cart.show');
    }
}


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

namespace App\Livewire\Storefront\Cart;

use Livewire\Component;

class Show extends Component
{
    public function render()
    {
        return view('livewire.storefront.cart.show');
    }
}


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

namespace App\Livewire\Storefront\Checkout;

use Livewire\Component;

class Show extends Component
{
    public function render()
    {
        return view('livewire.storefront.checkout.show');
    }
}


Output:
Chunk ID: 0cf16d
Wall time: 0.1625 seconds
Process exited with code 0
Original token count: 864
Output:
Laravel Framework 12.51.0

Usage:
  command [options] [arguments]

Options:
  -h, --help            Display help for the given command. When no command is given display help for the list command
      --silent          Do not output any message
  -q, --quiet           Only errors are displayed. All other output is suppressed
  -V, --version         Display this application version
      --ansi|--no-ansi  Force (or disable --no-ansi) ANSI output
  -n, --no-interaction  Do not ask any interactive question
      --env[=ENV]       The environment the command should run under
  -v|vv|vvv, --verbose  Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug

Available commands for the "make" namespace:
  make:cache-table          [cache:table] Create a migration for the cache database table
  make:cast                 Create a new custom Eloquent cast class
  make:channel              Create a new channel class
  make:class                Create a new class
  make:command              Create a new Artisan command
  make:component            Create a new view component class
  make:config               [config:make] Create a new configuration file
  make:controller           Create a new controller class
  make:enum                 Create a new enum
  make:event                Create a new event class
  make:exception            Create a new custom exception class
  make:factory              Create a new model factory
  make:interface            Create a new interface
  make:job                  Create a new job class
  make:job-middleware       Create a new job middleware class
  make:listener             Create a new event listener class
  make:livewire             Create a new Livewire component
  make:mail                 Create a new email class
  make:mcp-app-resource     Create a new MCP app resource class and linked view
  make:mcp-prompt           Create a new MCP prompt class
  make:mcp-resource         Create a new MCP resource class
  make:mcp-server           Create a new MCP server class
  make:mcp-tool             Create a new MCP tool class
  make:middleware           Create a new HTTP middleware class
  make:migration            Create a new migration file
  make:model                Create a new Eloquent model class
  make:notification         Create a new notification class
  make:notifications-table  [notifications:table] Create a migration for the notifications table
  make:observer             Create a new observer class
  make:policy               Create a new policy class
  make:provider             Create a new service provider class
  make:queue-batches-table  [queue:batches-table] Create a migration for the batches database table
  make:queue-failed-table   [queue:failed-table] Create a migration for the failed queue jobs database table
  make:queue-table          [queue:table] Create a migration for the queue jobs database table
  make:request              Create a new form request class
  make:resource             Create a new resource
  make:rule                 Create a new validation rule
  make:scope                Create a new scope class
  make:seeder               Create a new seeder class
  make:session-table        [session:table] Create a migration for the session database table
  make:test                 Create a new test class
  make:trait                Create a new trait
  make:view                 Create a new view


Laravel Framework 12.51.0

Usage:
  command [options] [arguments]

Options:
  -h, --help            Display help for the given command. When no command is given display help for the list command
      --silent          Do not output any message
  -q, --quiet           Only errors are displayed. All other output is suppressed
  -V, --version         Display this application version
      --ansi|--no-ansi  Force (or disable --no-ansi) ANSI output
  -n, --no-interaction  Do not ask any interactive question
      --env[=ENV]       The environment the command should run under
  -v|vv|vvv, --verbose  Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug

Available commands for the "make" namespace:
  make:cache-table          [cache:table] Create a migration for the cache database table
  make:cast                 Create a new custom Eloquent cast class
  make:channel              Create a new channel class
  make:class                Create a new class
  make:command              Create a new Artisan command
  make:component            Create a new view component class
  make:config               [config:make] Create a new configuration file
  make:controller           Create a new controller class
  make:enum                 Create a new enum
  make:event                Create a new event class
  make:exception            Create a new custom exception class
  make:factory              Create a new model factory
  make:interface            Create a new interface
  make:job                  Create a new job class
  make:job-middleware       Create a new job middleware class
  make:listener             Create a new event listener class
  make:livewire             Create a new Livewire component
  make:mail                 Create a new email class
  make:mcp-app-resource     Create a new MCP app resource class and linked view
  make:mcp-prompt           Create a new MCP prompt class
  make:mcp-resource         Create a new MCP resource class
  make:mcp-server           Create a new MCP server class
  make:mcp-tool             Create a new MCP tool class
  make:middleware           Create a new HTTP middleware class
  make:migration            Create a new migration file
  make:model                Create a new Eloquent model class
  make:notification         Create a new notification class
  make:notifications-table  [notifications:table] Create a migration for the notifications table
  make:observer             Create a new observer class
  make:policy               Create a new policy class
  make:provider             Create a new service provider class
  make:queue-batches-table  [queue:batches-table] Create a migration for the batches database table
  make:queue-failed-table   [queue:failed-table] Create a migration for the failed queue jobs database table
  make:queue-table          [queue:table] Create a migration for the queue jobs database table
  make:request              Create a new form request class
  make:resource             Create a new resource
  make:rule                 Create a new validation rule
  make:scope                Create a new scope class
  make:seeder               Create a new seeder class
  make:session-table        [session:table] Create a migration for the session database table
  make:test                 Create a new test class
  make:trait                Create a new trait
  make:view                 Create a new view


php artisan make:trait Livewire/Storefront/Concerns/ResolvesStorefrontState --no-interaction


Output:
Chunk ID: c3ea93
Wall time: 0.0507 seconds
Process exited with code 0
Original token count: 29
Output:

   INFO  Trait [app/Concerns/Livewire/Storefront/Concerns/ResolvesStorefrontState.php] created successfully.


php artisan make:trait Livewire/Storefront/Concerns/ResolvesStorefrontState --no-interaction

INFO  Trait [app/Concerns/Livewire/Storefront/Concerns/ResolvesStorefrontState.php] created successfully.


sed -n '1,80p' app/Concerns/Livewire/Storefront/Concerns/ResolvesStorefrontState.php


sed -n '1,80p' app/Concerns/Livewire/Storefront/Concerns/ResolvesStorefrontState.php

<?php

namespace App\Concerns\Livewire\Storefront\Concerns;

trait ResolvesStorefrontState
{
    //
}


Output:
Chunk ID: 3711ab
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 26
Output:
<?php

namespace App\Concerns\Livewire\Storefront\Concerns;

trait ResolvesStorefrontState
{
    //
}


Input:
*** Begin Patch
*** Delete File: /Users/fabianwesner/Herd/shop/app/Concerns/Livewire/Storefront/Concerns/ResolvesStorefrontState.php
*** End Patch


Success. Updated the following files:
D /Users/fabianwesner/Herd/shop/app/Concerns/Livewire/Storefront/Concerns/ResolvesStorefrontState.php

<?php

namespace App\Concerns\Livewire\Storefront\Concerns;

trait ResolvesStorefrontState
{
    //
}


Output: {"output":"Success. Updated the following files:\nD /Users/fabianwesner/Herd/shop/app/Concerns/Livewire/Storefront/Concerns/ResolvesStorefrontState.php\n","metadata":{"exit_code":0,"duration_seconds":0.0}}


sed -n '1,160p' app/Models/ProductVariant.php


sed -n '1,160p' app/Models/ProductVariant.php

<?php

namespace App\Models;

use App\Enums\InventoryPolicy;
use App\Enums\VariantStatus;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasOne;
use RuntimeException;

class ProductVariant extends Model
{
    /** @use HasFactory<\Database\Factories\ProductVariantFactory> */
    use HasFactory;

    /**
     * @var list<string>
     */
    protected $fillable = [
        'product_id',
        'sku',
        'barcode',
        'price_amount',
        'compare_at_amount',
        'currency',
        'weight_g',
        'requires_shipping',
        'is_default',
        'position',
        'status',
    ];

    /**
     * @var array<string, mixed>
     */
    protected $attributes = [
        'price_amount' => 0,
        'currency' => 'USD',
        'requires_shipping' => true,
        'is_default' => false,
        'position' => 0,
        'status' => 'active',
    ];

    protected static function booted(): void
    {
        static::addGlobalScope('current_store', function (Builder $builder): void {
            if (! app()->bound('current_store')) {
                return;
            }

            $store = app('current_store');

            if (! $store instanceof Store) {
                return;
            }

            $builder->whereHas('product', function (Builder $query) use ($store): void {
                $query
                    ->withoutGlobalScopes()
                    ->where('store_id', $store->getKey());
            });
        });

        static::saving(function (ProductVariant $variant): void {
            $variant->assertSkuIsUniqueForStore();
        });

        static::created(function (ProductVariant $variant): void {
            if ($variant->inventoryItem()->exists()) {
                return;
            }

            $storeId = Product::withoutGlobalScopes()
                ->whereKey($variant->product_id)
                ->value('store_id');

            if (! $storeId) {
                return;
            }

            InventoryItem::withoutGlobalScopes()->create([
                'store_id' => $storeId,
                'variant_id' => $variant->getKey(),
                'quantity_on_hand' => 0,
                'quantity_reserved' => 0,
                'policy' => InventoryPolicy::Deny,
            ]);
        });
    }

    private function assertSkuIsUniqueForStore(): void
    {
        $sku = trim((string) $this->sku);

        if ($sku === '') {
            return;
        }

        $storeId = Product::withoutGlobalScopes()
            ->whereKey($this->product_id)
            ->value('store_id');

        if ($storeId === null) {
            return;
        }

        $query = self::withoutGlobalScopes()
            ->where('sku', $sku)
            ->whereHas('product', function (Builder $query) use ($storeId): void {
                $query
                    ->withoutGlobalScopes()
                    ->where('store_id', $storeId);
            });

        if ($this->exists) {
            $query->whereKeyNot($this->getKey());
        }

        if ($query->exists()) {
            throw new RuntimeException("The SKU [{$sku}] is already used in this store.");
        }
    }

    /**
     * @return BelongsTo<Product, $this>
     */
    public function product(): BelongsTo
    {
        return $this->belongsTo(Product::class);
    }

    /**
     * @return HasOne<InventoryItem, $this>
     */
    public function inventoryItem(): HasOne
    {
        return $this->hasOne(InventoryItem::class, 'variant_id');
    }

    /**
     * @return BelongsToMany<ProductOptionValue, $this>
     */
    public function optionValues(): BelongsToMany
    {
        return $this->belongsToMany(ProductOptionValue::class, 'variant_option_values', 'variant_id', 'product_option_value_id');
    }

    public function isPurchasable(): bool
    {
        return $this->status === VariantStatus::Active;
    }

    /**
     * @return array<string, string>
     */


/bin/zsh
-lc
sed -n '1,160p' app/Models/Product.php

<?php

namespace App\Models;

use App\Enums\ProductStatus;
use App\Models\Concerns\BelongsToStore;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;

class Product extends Model
{
    /** @use HasFactory<\Database\Factories\ProductFactory> */
    use BelongsToStore, HasFactory;

    /**
     * @var list<string>
     */
    protected $fillable = [
        'store_id',
        'title',
        'handle',
        'status',
        'description_html',
        'vendor',
        'product_type',
        'tags',
        'published_at',
    ];

    /**
     * @var array<string, mixed>
     */
    protected $attributes = [
        'status' => 'draft',
        'tags' => '[]',
    ];

    /**
     * @return BelongsTo<Store, $this>
     */
    public function store(): BelongsTo
    {
        return $this->belongsTo(Store::class);
    }

    /**
     * @return HasMany<ProductOption, $this>
     */
    public function options(): HasMany
    {
        return $this->hasMany(ProductOption::class)->orderBy('position');
    }

    /**
     * @return HasMany<ProductVariant, $this>
     */
    public function variants(): HasMany
    {
        return $this->hasMany(ProductVariant::class)->orderBy('position');
    }

    /**
     * @return HasMany<ProductMedia, $this>
     */
    public function media(): HasMany
    {
        return $this->hasMany(ProductMedia::class)->orderBy('position');
    }

    /**
     * @return BelongsToMany<Collection, $this>
     */
    public function collections(): BelongsToMany
    {
        return $this->belongsToMany(Collection::class, 'collection_products')
            ->withPivot('position')
            ->orderByPivot('position');
    }

    public function isPublished(): bool
    {
        return $this->status === ProductStatus::Active && $this->published_at !== null;
    }

    /**
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'status' => ProductStatus::class,
            'tags' => 'array',
            'published_at' => 'datetime',
        ];
    }
}


/bin/zsh
-lc
sed -n '1,160p' app/Models/CartLine.php

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class CartLine extends Model
{
    /** @use HasFactory<\Database\Factories\CartLineFactory> */
    use HasFactory;

    public $timestamps = false;

    /**
     * @var list<string>
     */
    protected $fillable = [
        'cart_id',
        'variant_id',
        'quantity',
        'unit_price_amount',
        'line_subtotal_amount',
        'line_discount_amount',
        'line_total_amount',
    ];

    /**
     * @var array<string, mixed>
     */
    protected $attributes = [
        'quantity' => 1,
        'unit_price_amount' => 0,
        'line_subtotal_amount' => 0,
        'line_discount_amount' => 0,
        'line_total_amount' => 0,
    ];

    protected static function booted(): void
    {
        static::addGlobalScope('current_store', function (Builder $builder): void {
            if (! app()->bound('current_store')) {
                return;
            }

            $store = app('current_store');

            if (! $store instanceof Store) {
                return;
            }

            $builder->whereHas('cart', function (Builder $query) use ($store): void {
                $query
                    ->withoutGlobalScopes()
                    ->where('store_id', $store->getKey());
            });
        });
    }

    /**
     * @return BelongsTo<Cart, $this>
     */
    public function cart(): BelongsTo
    {
        return $this->belongsTo(Cart::class);
    }

    /**
     * @return BelongsTo<ProductVariant, $this>
     */
    public function variant(): BelongsTo
    {
        return $this->belongsTo(ProductVariant::class, 'variant_id');
    }

    /**
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'quantity' => 'integer',
            'unit_price_amount' => 'integer',
            'line_subtotal_amount' => 'integer',
            'line_discount_amount' => 'integer',
            'line_total_amount' => 'integer',
        ];
    }
}


/bin/zsh
-lc
sed -n '1,160p' app/Models/Cart.php

<?php

namespace App\Models;

use App\Enums\CartStatus;
use App\Models\Concerns\BelongsToStore;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;

class Cart extends Model
{
    /** @use HasFactory<\Database\Factories\CartFactory> */
    use BelongsToStore, HasFactory;

    /**
     * @var list<string>
     */
    protected $fillable = [
        'store_id',
        'customer_id',
        'currency',
        'cart_version',
        'status',
    ];

    /**
     * @var array<string, mixed>
     */
    protected $attributes = [
        'currency' => 'USD',
        'cart_version' => 1,
        'status' => 'active',
    ];

    /**
     * @return BelongsTo<Store, $this>
     */
    public function store(): BelongsTo
    {
        return $this->belongsTo(Store::class);
    }

    /**
     * @return BelongsTo<Customer, $this>
     */
    public function customer(): BelongsTo
    {
        return $this->belongsTo(Customer::class);
    }

    /**
     * @return HasMany<CartLine, $this>
     */
    public function lines(): HasMany
    {
        return $this->hasMany(CartLine::class);
    }

    /**
     * @return HasMany<Checkout, $this>
     */
    public function checkouts(): HasMany
    {
        return $this->hasMany(Checkout::class);
    }

    /**
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'cart_version' => 'integer',
            'status' => CartStatus::class,
        ];
    }
}


Output:
Chunk ID: 6d7072
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 407
Output:
<?php

namespace App\Models;

use App\Enums\CartStatus;
use App\Models\Concerns\BelongsToStore;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;

class Cart extends Model
{
    /** @use HasFactory<\Database\Factories\CartFactory> */
    use BelongsToStore, HasFactory;

    /**
     * @var list<string>
     */
    protected $fillable = [
        'store_id',
        'customer_id',
        'currency',
        'cart_version',
        'status',
    ];

    /**
     * @var array<string, mixed>
     */
    protected $attributes = [
        'currency' => 'USD',
        'cart_version' => 1,
        'status' => 'active',
    ];

    /**
     * @return BelongsTo<Store, $this>
     */
    public function store(): BelongsTo
    {
        return $this->belongsTo(Store::class);
    }

    /**
     * @return BelongsTo<Customer, $this>
     */
    public function customer(): BelongsTo
    {
        return $this->belongsTo(Customer::class);
    }

    /**
     * @return HasMany<CartLine, $this>
     */
    public function lines(): HasMany
    {
        return $this->hasMany(CartLine::class);
    }

    /**
     * @return HasMany<Checkout, $this>
     */
    public function checkouts(): HasMany
    {
        return $this->hasMany(Checkout::class);
    }

    /**
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'cart_version' => 'integer',
            'status' => CartStatus::class,
        ];
    }
}


Output:
Chunk ID: 3879f8
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 546
Output:
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class CartLine extends Model
{
    /** @use HasFactory<\Database\Factories\CartLineFactory> */
    use HasFactory;

    public $timestamps = false;

    /**
     * @var list<string>
     */
    protected $fillable = [
        'cart_id',
        'variant_id',
        'quantity',
        'unit_price_amount',
        'line_subtotal_amount',
        'line_discount_amount',
        'line_total_amount',
    ];

    /**
     * @var array<string, mixed>
     */
    protected $attributes = [
        'quantity' => 1,
        'unit_price_amount' => 0,
        'line_subtotal_amount' => 0,
        'line_discount_amount' => 0,
        'line_total_amount' => 0,
    ];

    protected static function booted(): void
    {
        static::addGlobalScope('current_store', function (Builder $builder): void {
            if (! app()->bound('current_store')) {
                return;
            }

            $store = app('current_store');

            if (! $store instanceof Store) {
                return;
            }

            $builder->whereHas('cart', function (Builder $query) use ($store): void {
                $query
                    ->withoutGlobalScopes()
                    ->where('store_id', $store->getKey());
            });
        });
    }

    /**
     * @return BelongsTo<Cart, $this>
     */
    public function cart(): BelongsTo
    {
        return $this->belongsTo(Cart::class);
    }

    /**
     * @return BelongsTo<ProductVariant, $this>
     */
    public function variant(): BelongsTo
    {
        return $this->belongsTo(ProductVariant::class, 'variant_id');
    }

    /**
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'quantity' => 'integer',
            'unit_price_amount' => 'integer',
            'line_subtotal_amount' => 'integer',
            'line_discount_amount' => 'integer',
            'line_total_amount' => 'integer',
        ];
    }
}


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

namespace App\Models;

use App\Enums\InventoryPolicy;
use App\Enums\VariantStatus;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasOne;
use RuntimeException;

class ProductVariant extends Model
{
    /** @use HasFactory<\Database\Factories\ProductVariantFactory> */
    use HasFactory;

    /**
     * @var list<string>
     */
    protected $fillable = [
        'product_id',
        'sku',
        'barcode',
        'price_amount',
        'compare_at_amount',
        'currency',
        'weight_g',
        'requires_shipping',
        'is_default',
        'position',
        'status',
    ];

    /**
     * @var array<string, mixed>
     */
    protected $attributes = [
        'price_amount' => 0,
        'currency' => 'USD',
        'requires_shipping' => true,
        'is_default' => false,
        'position' => 0,
        'status' => 'active',
    ];

    protected static function booted(): void
    {
        static::addGlobalScope('current_store', function (Builder $builder): void {
            if (! app()->bound('current_store')) {
                return;
            }

            $store = app('current_store');

            if (! $store instanceof Store) {
                return;
            }

            $builder->whereHas('product', function (Builder $query) use ($store): void {
                $query
                    ->withoutGlobalScopes()
                    ->where('store_id', $store->getKey());
            });
        });

        static::saving(function (ProductVariant $variant): void {
            $variant->assertSkuIsUniqueForStore();
        });

        static::created(function (ProductVariant $variant): void {
            if ($variant->inventoryItem()->exists()) {
                return;
            }

            $storeId = Product::withoutGlobalScopes()
                ->whereKey($variant->product_id)
                ->value('store_id');

            if (! $storeId) {
                return;
            }

            InventoryItem::withoutGlobalScopes()->create([
                'store_id' => $storeId,
                'variant_id' => $variant->getKey(),
                'quantity_on_hand' => 0,
                'quantity_reserved' => 0,
                'policy' => InventoryPolicy::Deny,
            ]);
        });
    }

    private function assertSkuIsUniqueForStore(): void
    {
        $sku = trim((string) $this->sku);

        if ($sku === '') {
            return;
        }

        $storeId = Product::withoutGlobalScopes()
            ->whereKey($this->product_id)
            ->value('store_id');

        if ($storeId === null) {
            return;
        }

        $query = self::withoutGlobalScopes()
            ->where('sku', $sku)
            ->whereHas('product', function (Builder $query) use ($storeId): void {
                $query
                    ->withoutGlobalScopes()
                    ->where('store_id', $storeId);
            });

        if ($this->exists) {
            $query->whereKeyNot($this->getKey());
        }

        if ($query->exists()) {
            throw new RuntimeException("The SKU [{$sku}] is already used in this store.");
        }
    }

    /**
     * @return BelongsTo<Product, $this>
     */
    public function product(): BelongsTo
    {
        return $this->belongsTo(Product::class);
    }

    /**
     * @return HasOne<InventoryItem, $this>
     */
    public function inventoryItem(): HasOne
    {
        return $this->hasOne(InventoryItem::class, 'variant_id');
    }

    /**
     * @return BelongsToMany<ProductOptionValue, $this>
     */
    public function optionValues(): BelongsToMany
    {
        return $this->belongsToMany(ProductOptionValue::class, 'variant_option_values', 'variant_id', 'product_option_value_id');
    }

    public function isPurchasable(): bool
    {
        return $this->status === VariantStatus::Active;
    }

    /**
     * @return array<string, string>
     */


Output:
Chunk ID: 5d8e11
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 569
Output:
<?php

namespace App\Models;

use App\Enums\ProductStatus;
use App\Models\Concerns\BelongsToStore;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;

class Product extends Model
{
    /** @use HasFactory<\Database\Factories\ProductFactory> */
    use BelongsToStore, HasFactory;

    /**
     * @var list<string>
     */
    protected $fillable = [
        'store_id',
        'title',
        'handle',
        'status',
        'description_html',
        'vendor',
        'product_type',
        'tags',
        'published_at',
    ];

    /**
     * @var array<string, mixed>
     */
    protected $attributes = [
        'status' => 'draft',
        'tags' => '[]',
    ];

    /**
     * @return BelongsTo<Store, $this>
     */
    public function store(): BelongsTo
    {
        return $this->belongsTo(Store::class);
    }

    /**
     * @return HasMany<ProductOption, $this>
     */
    public function options(): HasMany
    {
        return $this->hasMany(ProductOption::class)->orderBy('position');
    }

    /**
     * @return HasMany<ProductVariant, $this>
     */
    public function variants(): HasMany
    {
        return $this->hasMany(ProductVariant::class)->orderBy('position');
    }

    /**
     * @return HasMany<ProductMedia, $this>
     */
    public function media(): HasMany
    {
        return $this->hasMany(ProductMedia::class)->orderBy('position');
    }

    /**
     * @return BelongsToMany<Collection, $this>
     */
    public function collections(): BelongsToMany
    {
        return $this->belongsToMany(Collection::class, 'collection_products')
            ->withPivot('position')
            ->orderByPivot('position');
    }

    public function isPublished(): bool
    {
        return $this->status === ProductStatus::Active && $this->published_at !== null;
    }

    /**
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'status' => ProductStatus::class,
            'tags' => 'array',
            'published_at' => 'datetime',
        ];
    }
}


I’m making one small service addition first: a read-only current-cart lookup. That keeps the cart drawer from creating empty carts just because the layout rendered.


Input:
*** Begin Patch
*** Update File: /Users/fabianwesner/Herd/shop/app/Services/CartService.php
@@
     public function getOrCreateForSession(Store $store, ?Customer $customer = null): Cart
     {
         $sessionCartId = session('cart_id');
@@
         return $cart;
     }
+
+    public function currentForSession(Store $store, ?Customer $customer = null): ?Cart
+    {
+        if ($customer instanceof Customer) {
+            $customerCart = Cart::withoutGlobalScopes()
+                ->where('store_id', $store->getKey())
+                ->where('customer_id', $customer->getKey())
+                ->where('status', CartStatus::Active)
+                ->latest('id')
+                ->first();
+
+            if ($customerCart instanceof Cart) {
+                return $customerCart;
+            }
+        }
+
+        $sessionCartId = session('cart_id');
+
+        if (! $sessionCartId) {
+            return null;
+        }
+
+        return Cart::withoutGlobalScopes()
+            ->where('store_id', $store->getKey())
+            ->where('status', CartStatus::Active)
+            ->where(function ($query) use ($customer): void {
+                if ($customer instanceof Customer) {
+                    $query
+                        ->whereNull('customer_id')
+                        ->orWhere('customer_id', $customer->getKey());
+
+                    return;
+                }
+
+                $query->whereNull('customer_id');
+            })
+            ->find($sessionCartId);
+    }
*** End Patch