command: sed -n '1968,2005p' specs/07-SEEDERS-AND-TEST-DATA.md && sed -n '1871,1965p' specs/03-ADMIN-UI.md

aggregated_output:
---

### 3.16 NavigationSeeder

**Acme Fashion - Main Menu (handle: main-menu, title: "Main Menu"):**

| Position | Label | Type | Target |
|----------|-------|------|--------|
| 0 | Home | link | URL: / |
| 1 | New Arrivals | collection | resource_id: ID of "New Arrivals" collection |
| 2 | T-Shirts | collection | resource_id: ID of "T-Shirts" collection |
| 3 | Pants & Jeans | collection | resource_id: ID of "Pants & Jeans" collection |
| 4 | Sale | collection | resource_id: ID of "Sale" collection |

**Acme Fashion - Footer Menu (handle: footer-menu, title: "Footer Menu"):**

| Position | Label | Type | Target |
|----------|-------|------|--------|
| 0 | About Us | page | resource_id: ID of "About Us" page |
| 1 | FAQ | page | resource_id: ID of "FAQ" page |
| 2 | Shipping & Returns | page | resource_id: ID of "Shipping & Returns" page |
| 3 | Privacy Policy | page | resource_id: ID of "Privacy Policy" page |
| 4 | Terms of Service | page | resource_id: ID of "Terms of Service" page |

**Acme Electronics - Main Menu (handle: main-menu, title: "Main Menu"):**

| Position | Label | Type | Target |
|----------|-------|------|--------|
| 0 | Home | link | URL: / |
| 1 | Featured | collection | resource_id: ID of "Featured" collection |
| 2 | Accessories | collection | resource_id: ID of "Accessories" collection |

---

### 3.17 AnalyticsSeeder

**Acme Fashion - analytics_daily (30 days of daily data):**
**Route:** `GET /admin/navigation`

**Livewire component:** `App\Livewire\Admin\Navigation\Index`

**Blade view:** `resources/views/livewire/admin/navigation/index.blade.php`

### Component Requirements

| Property | Type | Purpose |
|----------|------|---------|
| `menus` | `Collection` | Navigation menus (Main Menu, Footer Menu) |
| `editingMenu` | `?NavigationMenu` | Menu being edited |
| `menuItems` | `array` | Nested array of menu items for the editing menu |
| `editingItem` | `?array` | Item currently being edited in the modal |
| `editingItemIndex` | `?int` | Index of item being edited |
| `itemLabel` | `string` | Item label |
| `itemType` | `string` | `link`, `page`, `collection`, `product` |
| `itemUrl` | `string` | URL if type is link |
| `itemResourceId` | `?int` | Resource ID if type is page/collection/product |

| Action | Behavior |
|--------|----------|
| `selectMenu(int $menuId)` | Loads a menu for editing |
| `addItem()` | Opens the item form modal in create mode |
| `editItem(int $index)` | Opens the item form modal in edit mode |
| `saveItem()` | Saves the item (add or update) |
| `removeItem(int $index)` | Removes an item |
| `reorderItems(array $order)` | Updates item positions (supports nesting) |
| `saveMenu()` | Persists all menu items |

### Layout Wireframe

```
+==================================================================+
| Navigation                                                        |
+==================================================================+
|                                                                   |
| MENUS                                                             |
| +----------------------------+  +----------------------------+   |
| | Main Menu        [Edit]    |  | Footer Menu       [Edit]   |   |
| +----------------------------+  +----------------------------+   |
|                                                                   |
| MENU EDITOR (shown when a menu is selected)                      |
| +--------------------------------------------------------------+ |
| | Main Menu                                    [+ Add item]    | |
| |                                                               | |
| | [drag] Home               link: /            [edit] [trash]  | |
| | [drag] Products           collection: All    [edit] [trash]  | |
| | [drag] About Us           page: About        [edit] [trash]  | |
| | [drag] Contact            link: /contact     [edit] [trash]  | |
| |                                                               | |
| |                                            [Save menu]       | |
| +--------------------------------------------------------------+ |
+==================================================================+
```

**Menu list:** Each menu displayed as a card with title and "Edit" button.

**Menu editor (shown when a menu is selected):**
- Menu title as heading
- "Add item" button (ghost, plus icon) in the header
- Sortable list of menu items, each row containing:
  - Drag handle (bars icon)
  - Item label (bold text)
  - Item type and target (small, muted text: e.g., "link: /about" or "collection: Summer Sale")
  - Edit button (pencil icon) and Delete button (trash icon)
- "Save menu" primary button at the bottom

#### Menu Item Form Modal

| Property | Value |
|----------|-------|
| Modal name | `item-form` |
| Max width | md |
| Heading | "Edit menu item" or "Add menu item" (dynamic) |

**Form fields:**
- Label input (placeholder: "About Us")
- Type select: Custom link, Page, Collection, Product (live-bound)
- Conditional fields based on type:
  - **Custom link:** URL input (placeholder: "https://...")
  - **Page/Collection/Product:** Resource select dropdown populated with available resources of that type
- Cancel button (ghost) and "Save item" button (primary)

---

## 15. Apps Page

**Route:** `GET /admin/apps`

**Livewire component:** `App\Livewire\Admin\Apps\Index`

**Blade view:** `resources/views/livewire/admin/apps/index.blade.php`

### Component Requirements


output:
Chunk ID: de057a
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 829
Output:
                ->where('store_id', $this->storeId)
                ->orderBy('title')
                ->limit(100)
                ->get(['id', 'title']),
            default => collect(),
        };
    }

    public function targetLabel(array $item): string
    {
        return match ($item['type']) {
            NavigationItemType::Page->value => 'page: '.$this->resourceTitle(Page::class, $item['resource_id']),
            NavigationItemType::Collection->value => 'collection: '.$this->resourceTitle(ProductCollection::class, $item['resource_id']),
            NavigationItemType::Product->value => 'product: '.$this->resourceTitle(Product::class, $item['resource_id']),
            default => 'link: '.($item['url'] ?: '#'),
        };
    }

    public function render(): mixed
    {
        return view('livewire.admin.navigation.index', [
            'menus' => $this->menus(),
            'selectedMenu' => $this->selectedMenu(),
            'resources' => $this->resourcesForType(),
        ])->layout('layouts.app', [
            'title' => __('Navigation'),
        ]);
    }

    private function store(): Store
    {
        $store = app('current_store');

        abort_unless($store instanceof Store, 404);

        return $store;
    }

    private function scopedStore(): Store
    {
        return Store::query()->whereKey($this->storeId)->firstOrFail();
    }

    private function selectedMenu(): NavigationMenu
    {
        abort_unless($this->selectedMenuId !== null, 404);

        return $this->menu($this->selectedMenuId);
    }

    private function menu(int $menuId): NavigationMenu
    {
        return NavigationMenu::withoutGlobalScopes()
            ->where('store_id', $this->storeId)
            ->whereKey($menuId)
            ->firstOrFail();
    }

    private function loadMenuItems(): void
    {
        $this->menuItems = NavigationItem::withoutGlobalScopes()
            ->where('menu_id', $this->selectedMenu()->getKey())
            ->orderBy('position')
            ->get()
            ->map(fn (NavigationItem $item): array => [
                'id' => $item->getKey(),
                'label' => $item->label,
                'type' => $item->type->value,
                'url' => $item->url,
                'resource_id' => $item->resource_id,
            ])
            ->all();
    }

    private function resourceExists(string $type, int $resourceId): bool
    {
        return match ($type) {
            NavigationItemType::Page->value => Page::withoutGlobalScopes()->where('store_id', $this->storeId)->whereKey($resourceId)->exists(),
            NavigationItemType::Collection->value => ProductCollection::withoutGlobalScopes()->where('store_id', $this->storeId)->whereKey($resourceId)->exists(),
            NavigationItemType::Product->value => Product::withoutGlobalScopes()->where('store_id', $this->storeId)->whereKey($resourceId)->exists(),
            default => true,
        };
    }

    private function resourceTitle(string $modelClass, mixed $resourceId): string
    {
        if (! is_numeric($resourceId)) {
            return 'Missing';
        }

        return $modelClass::withoutGlobalScopes()
            ->where('store_id', $this->storeId)
            ->whereKey((int) $resourceId)
            ->value('title') ?? 'Missing';
    }
}


output:
Chunk ID: dadb79
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 1369
Output:

---

### 3.16 NavigationSeeder

**Acme Fashion - Main Menu (handle: main-menu, title: "Main Menu"):**

| Position | Label | Type | Target |
|----------|-------|------|--------|
| 0 | Home | link | URL: / |
| 1 | New Arrivals | collection | resource_id: ID of "New Arrivals" collection |
| 2 | T-Shirts | collection | resource_id: ID of "T-Shirts" collection |
| 3 | Pants & Jeans | collection | resource_id: ID of "Pants & Jeans" collection |
| 4 | Sale | collection | resource_id: ID of "Sale" collection |

**Acme Fashion - Footer Menu (handle: footer-menu, title: "Footer Menu"):**

| Position | Label | Type | Target |
|----------|-------|------|--------|
| 0 | About Us | page | resource_id: ID of "About Us" page |
| 1 | FAQ | page | resource_id: ID of "FAQ" page |
| 2 | Shipping & Returns | page | resource_id: ID of "Shipping & Returns" page |
| 3 | Privacy Policy | page | resource_id: ID of "Privacy Policy" page |
| 4 | Terms of Service | page | resource_id: ID of "Terms of Service" page |

**Acme Electronics - Main Menu (handle: main-menu, title: "Main Menu"):**

| Position | Label | Type | Target |
|----------|-------|------|--------|
| 0 | Home | link | URL: / |
| 1 | Featured | collection | resource_id: ID of "Featured" collection |
| 2 | Accessories | collection | resource_id: ID of "Accessories" collection |

---

### 3.17 AnalyticsSeeder

**Acme Fashion - analytics_daily (30 days of daily data):**
**Route:** `GET /admin/navigation`

**Livewire component:** `App\Livewire\Admin\Navigation\Index`

**Blade view:** `resources/views/livewire/admin/navigation/index.blade.php`

### Component Requirements

| Property | Type | Purpose |
|----------|------|---------|
| `menus` | `Collection` | Navigation menus (Main Menu, Footer Menu) |
| `editingMenu` | `?NavigationMenu` | Menu being edited |
| `menuItems` | `array` | Nested array of menu items for the editing menu |
| `editingItem` | `?array` | Item currently being edited in the modal |
| `editingItemIndex` | `?int` | Index of item being edited |
| `itemLabel` | `string` | Item label |
| `itemType` | `string` | `link`, `page`, `collection`, `product` |
| `itemUrl` | `string` | URL if type is link |
| `itemResourceId` | `?int` | Resource ID if type is page/collection/product |

| Action | Behavior |
|--------|----------|
| `selectMenu(int $menuId)` | Loads a menu for editing |
| `addItem()` | Opens the item form modal in create mode |
| `editItem(int $index)` | Opens the item form modal in edit mode |
| `saveItem()` | Saves the item (add or update) |
| `removeItem(int $index)` | Removes an item |
| `reorderItems(array $order)` | Updates item positions (supports nesting) |
| `saveMenu()` | Persists all menu items |

### Layout Wireframe

```
+==================================================================+
| Navigation                                                        |
+==================================================================+
|                                                                   |
| MENUS                                                             |
| +----------------------------+  +----------------------------+   |
| | Main Menu        [Edit]    |  | Footer Menu       [Edit]   |   |
| +----------------------------+  +----------------------------+   |
|                                                                   |
| MENU EDITOR (shown when a menu is selected)                      |
| +--------------------------------------------------------------+ |
| | Main Menu                                    [+ Add item]    | |
| |                                                               | |
| | [drag] Home               link: /            [edit] [trash]  | |
| | [drag] Products           collection: All    [edit] [trash]  | |
| | [drag] About Us           page: About        [edit] [trash]  | |
| | [drag] Contact            link: /contact     [edit] [trash]  | |
| |                                                               | |
| |                                            [Save menu]       | |
| +--------------------------------------------------------------+ |
+==================================================================+
```

**Menu list:** Each menu displayed as a card with title and "Edit" button.

**Menu editor (shown when a menu is selected):**
- Menu title as heading
- "Add item" button (ghost, plus icon) in the header
- Sortable list of menu items, each row containing:
  - Drag handle (bars icon)
  - Item label (bold text)
  - Item type and target (small, muted text: e.g., "link: /about" or "collection: Summer Sale")
  - Edit button (pencil icon) and Delete button (trash icon)
- "Save menu" primary button at the bottom

#### Menu Item Form Modal

| Property | Value |
|----------|-------|
| Modal name | `item-form` |
| Max width | md |
| Heading | "Edit menu item" or "Add menu item" (dynamic) |

**Form fields:**
- Label input (placeholder: "About Us")
- Type select: Custom link, Page, Collection, Product (live-bound)
- Conditional fields based on type:
  - **Custom link:** URL input (placeholder: "https://...")
  - **Page/Collection/Product:** Resource select dropdown populated with available resources of that type
- Cancel button (ghost) and "Save item" button (primary)

---

## 15. Apps Page

**Route:** `GET /admin/apps`

**Livewire component:** `App\Livewire\Admin\Apps\Index`

**Blade view:** `resources/views/livewire/admin/apps/index.blade.php`

### Component Requirements


output:
Chunk ID: 2cd0b9
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 276
Output:
### navigation_items

Individual links within a navigation menu (supports nesting via position).

| Column | Type | Nullable | Default | Constraints | Notes |
|---|---|---|---|---|---|
| id | INTEGER | No | autoincrement | PRIMARY KEY | |
| menu_id | INTEGER | No | - | FK -> navigation_menus(id) ON DELETE CASCADE | Parent menu |
| type | TEXT | No | 'link' | CHECK IN ('link', 'page', 'collection', 'product') | Item type |
| label | TEXT | No | - | - | Display label |
| url | TEXT | Yes | NULL | - | Direct URL (used when type = 'link') |
| resource_id | INTEGER | Yes | NULL | - | FK to pages/collections/products (polymorphic) |
| position | INTEGER | No | 0 | - | Display ordering |

**Indexes:**

- `idx_navigation_items_menu_id` on (`menu_id`) - FK lookup, listing items per menu
- `idx_navigation_items_menu_position` on (`menu_id`, `position`) - Ordered item listing

        TEXT title
    }
    navigation_items {
        INTEGER id PK
        INTEGER menu_id FK
        TEXT type
        TEXT label
        INTEGER position
    }
```

### Epics 5-6 - Cart, Checkout, Orders, and Payments


cmd: rg "mainMenu|footerMenu|children|navigation" resources/views app/Livewire -g '*.php' -g '*.blade.php'


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


cmd: sed -n '1,180p' database/factories/NavigationItemFactory.php && sed -n '1,220p' database/seeders/NavigationItemSeeder.php


command: rg "mainMenu|footerMenu|children|navigation" resources/views app/Livewire -g '*.php' -g '*.blade.php'

type: search

cmd: rg 'mainMenu|footerMenu|children|navigation' resources/views app/Livewire -g '*.php' -g '*.blade.php'

query: mainMenu|footerMenu|children|navigation

path: views

aggregated_output:
resources/views/layouts/storefront.blade.php:                <nav class="hidden items-center gap-6 text-sm font-medium md:flex" aria-label="Main navigation">
app/Livewire/Admin/Pages/Form.php:    public function save(NavigationService $navigation): void
app/Livewire/Admin/Pages/Form.php:        $this->forgetNavigation($store, $navigation);
app/Livewire/Admin/Pages/Form.php:    public function deletePage(NavigationService $navigation): void
app/Livewire/Admin/Pages/Form.php:        $this->forgetNavigation($store, $navigation);
app/Livewire/Admin/Pages/Form.php:    private function forgetNavigation(Store $store, NavigationService $navigation): void
app/Livewire/Admin/Pages/Form.php:            ->each(fn (NavigationMenu $menu): mixed => $navigation->forget($menu));
app/Livewire/Admin/Navigation/Index.php:    public function saveMenu(NavigationService $navigation): void
app/Livewire/Admin/Navigation/Index.php:        $navigation->forget($menu);
app/Livewire/Admin/Navigation/Index.php:        return view('livewire.admin.navigation.index', [
resources/views/layouts/app/sidebar.blade.php:                    <flux:sidebar.item icon="bars-3" :href="route('admin.navigation.index')" :current="request()->routeIs('admin.navigation.*')" wire:navigate>
resources/views/livewire/admin/navigation/index.blade.php:                    wire:key="navigation-menu-{{ $menu->getKey() }}"
resources/views/livewire/admin/navigation/index.blade.php:                    <div wire:key="navigation-item-{{ $index }}-{{ $item['id'] ?? 'new' }}" class="flex flex-col gap-3 p-4 sm:flex-row sm:items-center">

status: completed


command: sed -n '1,260p' resources/views/layouts/storefront.blade.php && sed -n '1,220p' resources/views/livewire/storefront/home.blade.php

storefront.blade.php

type: read

home.blade.php

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

        @include('partials.head')

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

            Skip to main content

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

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

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

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

        @endif

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

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

                    @foreach ($mainLinks as $item)

                            {{ $item['label'] }}

                    @endforeach

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

        <livewire:storefront.cart-drawer />

            {{ $slot }}

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

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

Shop

                        @foreach ($footerLinks as $item)

                                {{ $item['label'] }}

                        @endforeach

Customer

                        Account
                        About

        @fluxScripts

    @php($hero = data_get($themeSettings, 'home.hero', []))

{{ data_get($hero, 'eyebrow') }}

{{ data_get($hero, 'heading', $store->name) }}

{{ data_get($hero, 'subheading') }}

                    <flux:button :href="data_get($hero, 'primary_url', '/collections')" wire:navigate variant="primary">{{ data_get($hero, 'primary_label', 'Shop') }}</flux:button>
                    <flux:button :href="data_get($hero, 'secondary_url', '/collections')" wire:navigate variant="ghost">{{ data_get($hero, 'secondary_label', 'View collections') }}</flux:button>

                @foreach ($featuredProducts->take(4) as $product)

getKey() }}">
                        <x-storefront.product-card :product="$product" />

                @endforeach

Featured collections

Curated paths through the current store catalog.

            All collections

            @foreach ($featuredCollections as $collection)
                handle) }}" class="group rounded-lg border border-zinc-200 bg-white p-5 transition hover:border-zinc-300 dark:border-zinc-800 dark:bg-zinc-900 dark:hover:border-zinc-700" wire:navigate wire:key="home-collection-{{ $collection->getKey() }}">

                        <flux:icon name="rectangle-stack" class="size-10" />

{{ $collection->title }}

{{ $collection->products_count }} products

            @endforeach

Featured products

Active products currently published for {{ $store->name }}.

            @foreach ($featuredProducts as $product)
                <x-storefront.product-card :product="$product" wire:key="home-product-{{ $product->getKey() }}" />
            @endforeach


command: sed -n '1,180p' database/factories/NavigationItemFactory.php && sed -n '1,220p' database/seeders/NavigationItemSeeder.php

NavigationItemSeeder.php

aggregated_output:
<?php

namespace Database\Factories;

use App\Enums\NavigationItemType;
use App\Models\NavigationMenu;
use Illuminate\Database\Eloquent\Factories\Factory;

/**
 * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\NavigationItem>
 */
class NavigationItemFactory extends Factory
{
    /**
     * Define the model's default state.
     *
     * @return array<string, mixed>
     */
    public function definition(): array
    {
        return [
            'menu_id' => NavigationMenu::factory(),
            'type' => NavigationItemType::Link,
            'label' => fake()->words(2, true),
            'url' => '/'.fake()->slug(),
            'resource_id' => null,
            'position' => 0,
        ];
    }

    public function page(int $pageId): static
    {
        return $this->state(fn (array $attributes): array => [
            'type' => NavigationItemType::Page,
            'url' => null,
            'resource_id' => $pageId,
        ]);
    }

    public function collection(int $collectionId): static
    {
        return $this->state(fn (array $attributes): array => [
            'type' => NavigationItemType::Collection,
            'url' => null,
            'resource_id' => $collectionId,
        ]);
    }

    public function product(int $productId): static
    {
        return $this->state(fn (array $attributes): array => [
            'type' => NavigationItemType::Product,
            'url' => null,
            'resource_id' => $productId,
        ]);
    }
}
<?php

namespace Database\Seeders;

use App\Models\Collection as ProductCollection;
use App\Models\NavigationItem;
use App\Models\NavigationMenu;
use App\Models\Page;
use App\Models\Store;
use Illuminate\Database\Seeder;

class NavigationItemSeeder extends Seeder
{
    /**
     * Run the database seeds.
     */
    public function run(): void
    {
        Store::query()->get()->each(function (Store $store): void {
            $menus = NavigationMenu::withoutGlobalScopes()
                ->where('store_id', $store->getKey())
                ->get()
                ->keyBy('handle');

            $this->replaceItems($menus->get('main-menu'), $this->mainMenuItems($store));
            $this->replaceItems($menus->get('footer-menu'), $this->footerMenuItems($store));
        });
    }

    /**
     * @param  array<int, array{label: string, type: string, url?: string|null, resource_id?: int|null}>  $items
     */
    private function replaceItems(?NavigationMenu $menu, array $items): void
    {
        if ($menu === null) {
            return;
        }

        NavigationItem::withoutGlobalScopes()
            ->where('menu_id', $menu->getKey())
            ->delete();

        foreach ($items as $position => $item) {
            NavigationItem::withoutGlobalScopes()->create([
                'menu_id' => $menu->getKey(),
                'type' => $item['type'],
                'label' => $item['label'],
                'url' => $item['url'] ?? null,
                'resource_id' => $item['resource_id'] ?? null,
                'position' => $position,
            ]);
        }
    }

    /**
     * @return array<int, array{label: string, type: string, url?: string|null, resource_id?: int|null}>
     */
    private function mainMenuItems(Store $store): array
    {
        $newArrivals = $this->collection($store, 'new-arrivals') ?? $this->collection($store, 'featured');
        $secondaryCollection = $this->collection($store, 't-shirts') ?? $this->collection($store, 'accessories');
        $about = $this->page($store, 'about');

        return array_values(array_filter([
            ['label' => 'Collections', 'type' => 'link', 'url' => '/collections'],
            $newArrivals ? ['label' => $newArrivals->title, 'type' => 'collection', 'resource_id' => $newArrivals->getKey()] : null,
            $secondaryCollection ? ['label' => $secondaryCollection->title, 'type' => 'collection', 'resource_id' => $secondaryCollection->getKey()] : null,
            ['label' => 'Search', 'type' => 'link', 'url' => '/search'],
            $about ? ['label' => 'About', 'type' => 'page', 'resource_id' => $about->getKey()] : null,
        ]));
    }

    /**
     * @return array<int, array{label: string, type: string, url?: string|null, resource_id?: int|null}>
     */
    private function footerMenuItems(Store $store): array
    {
        $sale = $this->collection($store, 'sale');
        $about = $this->page($store, 'about');
        $faq = $this->page($store, 'faq');

        return array_values(array_filter([
            $sale ? ['label' => 'Sale', 'type' => 'collection', 'resource_id' => $sale->getKey()] : null,
            $about ? ['label' => 'About', 'type' => 'page', 'resource_id' => $about->getKey()] : null,
            $faq ? ['label' => 'FAQ', 'type' => 'page', 'resource_id' => $faq->getKey()] : null,
            ['label' => 'Account', 'type' => 'link', 'url' => '/account'],
        ]));
    }

    private function collection(Store $store, string $handle): ?ProductCollection
    {
        return ProductCollection::withoutGlobalScopes()
            ->where('store_id', $store->getKey())
            ->where('handle', $handle)
            ->first();
    }

    private function page(Store $store, string $handle): ?Page
    {
        return Page::withoutGlobalScopes()
            ->where('store_id', $store->getKey())
            ->where('handle', $handle)
            ->first();
    }
}


output:
Chunk ID: 215964
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 399
Output:
resources/views/layouts/storefront.blade.php:                <nav class="hidden items-center gap-6 text-sm font-medium md:flex" aria-label="Main navigation">
app/Livewire/Admin/Pages/Form.php:    public function save(NavigationService $navigation): void
app/Livewire/Admin/Pages/Form.php:        $this->forgetNavigation($store, $navigation);
app/Livewire/Admin/Pages/Form.php:    public function deletePage(NavigationService $navigation): void
app/Livewire/Admin/Pages/Form.php:        $this->forgetNavigation($store, $navigation);
app/Livewire/Admin/Pages/Form.php:    private function forgetNavigation(Store $store, NavigationService $navigation): void
app/Livewire/Admin/Pages/Form.php:            ->each(fn (NavigationMenu $menu): mixed => $navigation->forget($menu));
app/Livewire/Admin/Navigation/Index.php:    public function saveMenu(NavigationService $navigation): void
app/Livewire/Admin/Navigation/Index.php:        $navigation->forget($menu);
app/Livewire/Admin/Navigation/Index.php:        return view('livewire.admin.navigation.index', [
resources/views/layouts/app/sidebar.blade.php:                    <flux:sidebar.item icon="bars-3" :href="route('admin.navigation.index')" :current="request()->routeIs('admin.navigation.*')" wire:navigate>
resources/views/livewire/admin/navigation/index.blade.php:                    wire:key="navigation-menu-{{ $menu->getKey() }}"
resources/views/livewire/admin/navigation/index.blade.php:                    <div wire:key="navigation-item-{{ $index }}-{{ $item['id'] ?? 'new' }}" class="flex flex-col gap-3 p-4 sm:flex-row sm:items-center">


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

        @include('partials.head')

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

            Skip to main content

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

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

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

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

        @endif

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

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

                    @foreach ($mainLinks as $item)

                            {{ $item['label'] }}

                    @endforeach

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

        <livewire:storefront.cart-drawer />

            {{ $slot }}

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

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

Shop

                        @foreach ($footerLinks as $item)

                                {{ $item['label'] }}

                        @endforeach

Customer

                        Account
                        About

        @fluxScripts

    @php($hero = data_get($themeSettings, 'home.hero', []))

{{ data_get($hero, 'eyebrow') }}

{{ data_get($hero, 'heading', $store->name) }}

{{ data_get($hero, 'subheading') }}

                    <flux:button :href="data_get($hero, 'primary_url', '/collections')" wire:navigate variant="primary">{{ data_get($hero, 'primary_label', 'Shop') }}</flux:button>
                    <flux:button :href="data_get($hero, 'secondary_url', '/collections')" wire:navigate variant="ghost">{{ data_get($hero, 'secondary_label', 'View collections') }}</flux:button>

                @foreach ($featuredProducts->take(4) as $product)

getKey() }}">
                        <x-storefront.product-card :product="$product" />

                @endforeach

Featured collections

Curated paths through the current store catalog.

            All collections

            @foreach ($featuredCollections as $collection)
                handle) }}" class="group rounded-lg border border-zinc-200 bg-white p-5 transition hover:border-zinc-300 dark:border-zinc-800 dark:bg-zinc-900 dark:hover:border-zinc-700" wire:navigate wire:key="home-collection-{{ $collection->getKey() }}">

                        <flux:icon name="rectangle-stack" class="size-10" />

{{ $collection->title }}

{{ $collection->products_count }} products

            @endforeach

Featured products

Active products currently published for {{ $store->name }}.

            @foreach ($featuredProducts as $product)
                <x-storefront.product-card :product="$product" wire:key="home-product-{{ $product->getKey() }}" />
            @endforeach


output:
Chunk ID: 0d339e
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 1362
Output:
<?php

namespace Database\Factories;

use App\Enums\NavigationItemType;
use App\Models\NavigationMenu;
use Illuminate\Database\Eloquent\Factories\Factory;

/**
 * @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\NavigationItem>
 */
class NavigationItemFactory extends Factory
{
    /**
     * Define the model's default state.
     *
     * @return array<string, mixed>
     */
    public function definition(): array
    {
        return [
            'menu_id' => NavigationMenu::factory(),
            'type' => NavigationItemType::Link,
            'label' => fake()->words(2, true),
            'url' => '/'.fake()->slug(),
            'resource_id' => null,
            'position' => 0,
        ];
    }

    public function page(int $pageId): static
    {
        return $this->state(fn (array $attributes): array => [
            'type' => NavigationItemType::Page,
            'url' => null,
            'resource_id' => $pageId,
        ]);
    }

    public function collection(int $collectionId): static
    {
        return $this->state(fn (array $attributes): array => [
            'type' => NavigationItemType::Collection,
            'url' => null,
            'resource_id' => $collectionId,
        ]);
    }

    public function product(int $productId): static
    {
        return $this->state(fn (array $attributes): array => [
            'type' => NavigationItemType::Product,
            'url' => null,
            'resource_id' => $productId,
        ]);
    }
}
<?php

namespace Database\Seeders;

use App\Models\Collection as ProductCollection;
use App\Models\NavigationItem;
use App\Models\NavigationMenu;
use App\Models\Page;
use App\Models\Store;
use Illuminate\Database\Seeder;

class NavigationItemSeeder extends Seeder
{
    /**
     * Run the database seeds.
     */
    public function run(): void
    {
        Store::query()->get()->each(function (Store $store): void {
            $menus = NavigationMenu::withoutGlobalScopes()
                ->where('store_id', $store->getKey())
                ->get()
                ->keyBy('handle');

            $this->replaceItems($menus->get('main-menu'), $this->mainMenuItems($store));
            $this->replaceItems($menus->get('footer-menu'), $this->footerMenuItems($store));
        });
    }

    /**
     * @param  array<int, array{label: string, type: string, url?: string|null, resource_id?: int|null}>  $items
     */
    private function replaceItems(?NavigationMenu $menu, array $items): void
    {
        if ($menu === null) {
            return;
        }

        NavigationItem::withoutGlobalScopes()
            ->where('menu_id', $menu->getKey())
            ->delete();

        foreach ($items as $position => $item) {
            NavigationItem::withoutGlobalScopes()->create([
                'menu_id' => $menu->getKey(),
                'type' => $item['type'],
                'label' => $item['label'],
                'url' => $item['url'] ?? null,
                'resource_id' => $item['resource_id'] ?? null,
                'position' => $position,
            ]);
        }
    }

    /**
     * @return array<int, array{label: string, type: string, url?: string|null, resource_id?: int|null}>
     */
    private function mainMenuItems(Store $store): array
    {
        $newArrivals = $this->collection($store, 'new-arrivals') ?? $this->collection($store, 'featured');
        $secondaryCollection = $this->collection($store, 't-shirts') ?? $this->collection($store, 'accessories');
        $about = $this->page($store, 'about');

        return array_values(array_filter([
            ['label' => 'Collections', 'type' => 'link', 'url' => '/collections'],
            $newArrivals ? ['label' => $newArrivals->title, 'type' => 'collection', 'resource_id' => $newArrivals->getKey()] : null,
            $secondaryCollection ? ['label' => $secondaryCollection->title, 'type' => 'collection', 'resource_id' => $secondaryCollection->getKey()] : null,
            ['label' => 'Search', 'type' => 'link', 'url' => '/search'],
            $about ? ['label' => 'About', 'type' => 'page', 'resource_id' => $about->getKey()] : null,
        ]));
    }

    /**
     * @return array<int, array{label: string, type: string, url?: string|null, resource_id?: int|null}>
     */
    private function footerMenuItems(Store $store): array
    {
        $sale = $this->collection($store, 'sale');
        $about = $this->page($store, 'about');
        $faq = $this->page($store, 'faq');

        return array_values(array_filter([
            $sale ? ['label' => 'Sale', 'type' => 'collection', 'resource_id' => $sale->getKey()] : null,
            $about ? ['label' => 'About', 'type' => 'page', 'resource_id' => $about->getKey()] : null,
            $faq ? ['label' => 'FAQ', 'type' => 'page', 'resource_id' => $faq->getKey()] : null,
            ['label' => 'Account', 'type' => 'link', 'url' => '/account'],
        ]));
    }

    private function collection(Store $store, string $handle): ?ProductCollection
    {
        return ProductCollection::withoutGlobalScopes()
            ->where('store_id', $store->getKey())
            ->where('handle', $handle)
            ->first();
    }

    private function page(Store $store, string $handle): ?Page
    {
        return Page::withoutGlobalScopes()
            ->where('store_id', $store->getKey())
            ->where('handle', $handle)
            ->first();
    }
}


queries:
wire:sort sortable list sort item group
wire sort Livewire 4


server: laravel-boost

tool: search-docs

type: text

text:
# Search Results

## Wire Sort
Wire Sort | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/wire-sort

Livewire provides drag-and-drop sorting through the `wire:sort` directive. Add it to a parent element and use `wire:sort:item` on each child to make lists sortable with smooth animations out of the box.

---

## Wire Sort Basic usage
Wire Sort | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/wire-sort

## Basic usage

To make a list sortable, add `wire:sort` to the parent element with a handler method name, and `wire:sort:item` to each child with a unique identifier:

```php
<?php

use Livewire\Component;

new class extends Component {
    public TodoList $list;

    public function handleSort($id, $position)
    {
        $task = $this->list->tasks()->findOrFail($id);

        // Update the task's position and re-order other tasks...
    }
};
```

```blade
<ul wire:sort="handleSort">
    @foreach ($list->tasks as $task)
        <li wire:key="{{ $task->id }}" wire:sort:item="{{ $task->id }}">
            {{ $task->title }}
        </li>
    @endforeach
</ul>
```

When a user drags and drops an item to a new position, Livewire will call your handler with two parameters: the item's identifier (from `wire:sort:item`) and the new zero-based position.

You are responsible for persisting the new order in your database.

---

## Wire Sort Sorting across groups
Wire Sort | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/wire-sort

## Sorting across groups

To allow dragging items between multiple lists, use `wire:sort:group` with the same group name on each container.

To identify which group the item was dropped into, add `wire:sort:group-id` to each container. Its value will be passed as a third parameter to your handler:

```php
<?php

use Livewire\Component;
use Livewire\Attributes\Computed;
use App\Models\Card;

new class extends Component {
    public Board $board;

    #[Computed]
    public function columns()
    {
        return $this->board->columns;
    }

    public function handleSort($id, $position, $columnId)
    {
        $card = $this->board->cards()->findOrFail($id);

        // Update the card's position and re-order other cards...
    }
};
```

```blade
<div>
    @foreach ($this->columns as $column)
        <ul wire:sort="handleSort" wire:sort:group="cards" wire:sort:group-id="{{ $column->id }}">
            @foreach ($column->cards as $card)
                <li wire:key="{{ $card->id }}" wire:sort:item="{{ $card->id }}">
                    {{ $card->title }}
                </li>
            @endforeach
        </ul>
    @endforeach
</div>
```

When an item is dragged to a different group, only the destination group's handler fires.

---

## Wire Sort Reference
Wire Sort | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/wire-sort

## Reference

```blade
wire:sort="method"
wire:sort:item="id"
wire:sort:group="name"
wire:sort:group-id="identifier"
wire:sort:handle
wire:sort:ignore
```

This directive has no modifiers.

---

## Wire Sort Sort handles
Wire Sort | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/wire-sort

## Sort handles

By default, users can drag an item by clicking anywhere on it. To restrict dragging to a specific handle, use `wire:sort:handle`:

```blade
<ul wire:sort="handleSort">
    @foreach ($list->tasks as $task)
        <li wire:key="{{ $task->id }}" wire:sort:item="{{ $task->id }}">
            <div wire:sort:handle>
                <!-- Drag icon... -->
            </div>

            {{ $task->title }}
        </li>
    @endforeach
</ul>
```

Now users can only initiate a drag from the handle element.

---

## Wire Sort Ignoring elements
Wire Sort | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/wire-sort

## Ignoring elements

To prevent specific areas from triggering drag operations, use `wire:sort:ignore`. This is useful for buttons or other interactive elements inside sortable items:

```blade
<ul wire:sort="handleSort">
    @foreach ($list->tasks as $task)
        <li wire:key="{{ $task->id }}" wire:sort:item="{{ $task->id }}">
            {{ $task->title }}

            <div wire:sort:ignore>
                <button type="button">Edit</button>
            </div>
        </li>
    @endforeach
</ul>
```

---

## Undocumented Features Todo Existing Docs to Update
Undocumented Features Todo | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/undocumented-features-todo

## Existing Docs to Update

- **lazy.md** - Already updated with defer and bundle features
- **actions.md** - Already updated with async actions
- **islands.md** - Already updated with renderIsland/streamIsland
- **components.md** - Already updated with convert command
- **wire-sort.md** - Already created and documented

---

## Wire Confirm
Wire Confirm | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/wire-confirm

Before performing dangerous actions in Livewire, you may want to provide your users with some sort of visual confirmation.

Livewire makes this easy to do by adding `wire:confirm` in addition to any action (`wire:click`, `wire:submit`, etc.).

Here's an example of adding a confirmation dialog to a "Delete post" button:

```blade
<button
    type="button"
    wire:click="delete"
    wire:confirm="Are you sure you want to delete this post?"
>
    Delete post <!-- [tl! highlight:-2,1] -->
</button>
```

When a user clicks "Delete post", Livewire will trigger a confirmation dialog (The default browser confirmation alert). If the user hits escape or presses cancel, the action won't be performed. If they press "OK", the action will be completed.

---

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

## See also

- **[wire:show](/docs/4.x/wire-show)** — Toggle visibility with CSS display
- **[Loading States](/docs/4.x/loading-states)** — Show loading indicators during requests
- **[Alpine Transitions](https://alpinejs.dev/directives/transition)** — For more complex animation needs

---

## Upgrading New features in v4
Upgrading | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/upgrading

## New features in v4

Livewire v4 introduces several powerful new features you can start using immediately:

### Component features

**Single-file and multi-file components**

v4 introduces new component formats alongside the traditional class-based approach. Single-file components combine PHP and Blade in one file, while multi-file components organize PHP, Blade, JavaScript, and tests in a directory.

By default, view-based component files are prefixed with a ⚡ emoji to distinguish them from regular Blade files in your editor and searches. This can be disabled via the `make_command.emoji` config.

```bash
php artisan make:livewire create-post        # Single-file (default)
php artisan make:livewire create-post --mfc  # Multi-file
php artisan livewire:convert create-post     # Convert between formats
```

[Learn more about component formats →](/docs/4.x/components)

**Slots and attribute forwarding**

Components now support slots and automatic attribute bag forwarding using `{{ $attributes }}`, making component composition more flexible.

[Learn more about nesting components →](/docs/4.x/nesting)

**JavaScript in view-based components**

View-based components can now include `<script>` tags without the `@script` wrapper. These scripts are served as separate cached files for better performance and automatic `$wire` binding:

```blade
<div>
    <!-- Your component template -->
</div>

<script>
    // $wire is automatically bound as 'this'
    this.count++  // Same as $wire.count++

    // $wire is still available if preferred
    $wire.save()
</script>
```

[Learn more about JavaScript in components →](/docs/4.x/javascript)

### Islands

Islands allow you to create isolated regions within a component that update independently, dramatically improving performance without creating separate child components.

```blade
@island(name: 'stats', lazy: true)
    <div>{{ $this->expensiveStats }}</div>
@endisland
```

[Learn more about islands →](/docs/4.x/islands)

### Loading improvements

**Deferred loading**

In addition to lazy loading (viewport-based), components can now be deferred to load immediately after the initial page load:

```blade
<livewire:revenue defer />
```

```php
#[Defer]
class Revenue extends Component { ... }
```

**Bundled loading**

Control whether multiple lazy/deferred components load in parallel or bundled together:

```blade
<livewire:revenue lazy.bundle />
<livewire:expenses defer.bundle />
```

```php
#[Lazy(bundle: true)]
class Revenue extends Component { ... }
```

[Learn more about lazy and deferred loading →](/docs/4.x/lazy)

### Async actions

Run actions in parallel without blocking other requests using the `.async` modifier or `#[Async]` attribute:

```blade
<button wire:click.async="logActivity">Track</button>
```

```php
#[Async]
public function logActivity() { ... }
```

[Learn more about async actions →](/docs/4.x/actions#parallel-execution-with-async)

### New directives and modifiers

**`wire:sort` - Drag-and-drop sorting**

Built-in support for sortable lists with drag-and-drop:

```blade
<ul wire:sort="updateOrder">
    @foreach ($items as $item)
        <li wire:sort:item="{{ $item->id }}" wire:key="{{ $item->id }}">{{ $item->name }}</li>
    @endforeach
</ul>
```

[Learn more about wire:sort →](/docs/4.x/wire-sort)

**`wire:intersect` - Viewport intersection**

Run actions when elements enter or leave the viewport, similar to Alpine's [`x-intersect`](https://alpinejs.dev/plugins/intersect):

```blade
<!-- Basic usage -->
<div wire:intersect="loadMore">...</div>

<!-- With modifiers -->
<div wire:intersect.once="trackView">...</div>
<div wire:intersect:leave="pauseVideo">...</div>
<div wire:intersect.half="loadMore">...</div>
<div wire:intersect.full="startAnimation">...</div>

<!-- With options -->
<div wire:intersect.margin.200px="loadMore">...</div>
<div wire:intersect.threshold.50="trackScroll">...</div>
```

Available modifiers:
- `.once` - Fire only once
- `.half` - Wait until half is visible
- `.full` - Wait until fully visible
- `.threshold.X` - Custom visibility percentage (0-100)
- `.margin.Xpx` or `.margin.X%` - Intersection margin

[Learn more about wire:intersect →](/docs/4.x/wire-intersect)

**`wire:ref` - Element references**

Easily reference and interact with elements in your template:

```blade
<div wire:ref="modal">
    <!-- Modal content -->
</div>

<button wire:click="$js.scrollToModal">Scroll to modal</button>

<script>
    this.$js.scrollToModal = () => {
        this.$refs.modal.scrollIntoView()
    }
</script>
```

[Learn more about wire:ref →](/docs/4.x/wire-ref)

**`.renderless` modifier**

Skip component re-rendering directly from the template:

```blade
<button wire:click.renderless="trackClick">Track</button>
```

This is an alternative to the `#[Renderless]` attribute for actions that don't need to update the UI.

**`.preserve-scroll` modifier**

Preserve scroll position during updates to prevent layout jumps:

```blade
<button wire:click.preserve-scroll="loadMore">Load More</button>
```

**`data-loading` attribute**

Every element that triggers a network request automatically receives a `data-loading` attribute, making it easy to style loading states with Tailwind:

```blade
<button wire:click="save" class="data-loading:opacity-50 data-loading:pointer-events-none">
    Save Changes
</button>
```

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

### JavaScript improvements

**`$errors` magic property**

Access your component's error bag from JavaScript:

```blade
<div wire:show="$errors.has('email')">
    <span wire:text="$errors.first('email')"></span>
</div>
```

[Learn more about validation →](/docs/4.x/validation)

**`$intercept` magic**

Intercept and modify Livewire requests from JavaScript:

```blade
<script>
this.$intercept('save', ({ ... }) => {
    // ...
})
</script>
```

[Learn more about JavaScript interceptors →](/docs/4.x/javascript#interceptors)

**Island targeting from JavaScript**

Trigger island renders directly from the template:

```blade
<button wire:click="loadMore" wire:island.append="stats">
    Load more
</button>
```

[Learn more about islands →](/docs/4.x/islands)

---

## Upgrade Guide Scratch File new features (link to relevant docs files for each of these of course)
Upgrade Guide Scratch File | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/upgrade-guide-scratch-file

## new features (link to relevant docs files for each of these of course)

not sure where to note but view-based component files are prefixed withemojis by deatult. you can change make_command config for this and never see the emojis at all. however they are good to:
- distinguish plain blade files from livewire view-based files in your filesystem and editor searching and what now.

- data-loading attribute gets added to every element that triggered a network request. not sure if this is documented.
- wire:[click|...].preserve-scroll="..." - this helper makes it so that before a request comes back and updates the html fo the page it preserves scroll position so there's no layout jump while scrolling or anything. not sure if docuemnted. might need to look at source to determine where it goes
- `wire:intersect`. this is now a new api to mirror alpine's x-intersect. it allows you to run an action when an element enters the viewport. `wire:intersect:enter|leave` are avaialble modifier type things. also things like .once (only fire once) .half (wait till half is vislble/invisible) .full (waits till all of it is inthe viewport to fire) .threshold.50 allows you to manually configure the amoyunt visible needed. margin.200px (allows you to set a margin of intersection) or all of them like so: margin.10%.25px.25.25px
- SLots. components now have slots and attribute bag forwarding {{ $attributes }}
- `wire:ref` allows you to easily access element from js and dispatch events to targeted components inside a loop or something
- `wire:sort` new API to allow drag-sorting of items in a group. suports nesting and drag-between-groups type use-cases
- Livewire now ships with an `$errors` magic that gives you client-side access to the compoennts's error bag (this might need to be documented somewhere actually)
- Islands. obviously this is like the big feature of V4. there are docs for it.
- wire:click="$island('foo', { mode: 'prepend' })" i think is an undocumented feature for directly calling the render of an island and optionally pass a render mode like append or something
- `$intercept` magic. like `$wire.$intercept()` or `this.$intercept()` optionally accepts name of action to intercept and callback with hooks as params. this is doucmented you can reference that.
- `.async` wire:??? modifier and #[Async] attribute.
- `.renderless` is now an avaialble modifier like .async as an alternative to the existing #[Renderless] action/method attribute
- CSP-mode. If you set `csp_safe` to true in your config livewire will now use the alpine CSP build and your whole app will avoid unsafe eval violations. there are many restrictions when using this mode. you can no longer put complex js or global expressions like: `wire:click="doSomething($event.detail.foo)"` or global things like `wire:click="redirectHere(window.location.href)"`
- defer loading in addition to lazy. <livewire:component defer> attribute #[Defer] attribute for class (same signatures as lazy)
- can use additional `lazy.bundle` or `defer.bundle` additional syntax to flag the lazy load to bundle multiple lazy loads instead of them being isolated/parralel. There's also `#[Defer|Lazy(bundle: true)]` for this.
-kinda big one. view-based components can have <script></script> at the bottom with now `@script` wrapper and they work differently. their source is made available via a faux js file to frontend so caching and native module code can work. also they automatically bind this. ($wire is what this is ) and $wire is still available but this. is preferred.

---

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

## Reference

```blade
wire:model="propertyName"
wire:model="property.nested"
wire:model="property['nested']"
wire:model="property[0]"
```

### Modifiers

| Modifier | Description |
|----------|-------------|
| `.live` | Send updates to the server |
| `.blur` | Only update on blur |
| `.change` | Only update on change |
| `.enter` | Only update on enter key |
| `.lazy` | Update on change and send network request (v3 compatible) |
| `.debounce.Xms` | Debounce updates (use with `.live`) |
| `.throttle.Xms` | Throttle updates (use with `.live`) |
| `.number` | Cast value to `int` on the server |
| `.boolean` | Cast value to `bool` on the server |
| `.fill` | Use initial value from HTML `value` attribute |
| `.deep` | Also listen to events from child elements |
| `.preserve-scroll` | Maintain scroll position during updates |

---

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

## Magic actions

Livewire provides a set of "magic" actions that allow you to perform common tasks in your components without defining custom methods. These magic actions can be used within event listeners defined in your Blade templates.

### `$parent`

The `$parent` magic variable allows you to access parent component properties and call parent component actions from a child component:

```blade
<button wire:click="$parent.removePost({{ $post->id }})">Remove</button>
```

In the above example, if a parent component has a `removePost()` action, a child can call it directly from its Blade template using `$parent.removePost()`.

### `$set`

The `$set` magic action allows you to update a property in your Livewire component directly from the Blade template. To use `$set`, provide the property you want to update and the new value as arguments:

```blade
<button wire:click="$set('query', '')">Reset Search</button>
```

In this example, when the button is clicked, a network request is dispatched that sets the `$query` property in the component to `''`.

### `$refresh`

The `$refresh` action triggers a re-render of your Livewire component. This can be useful when updating the component's view without changing any property values:

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

When the button is clicked, the component will re-render, allowing you to see the latest changes in the view.

### `$toggle`

The `$toggle` action is used to toggle the value of a boolean property in your Livewire component:

```blade
<button wire:click="$toggle('sortAsc')">
    Sort {{ $sortAsc ? 'Descending' : 'Ascending' }}
</button>
```

In this example, when the button is clicked, the `$sortAsc` property in the component will toggle between `true` and `false`.

### `$dispatch`

The `$dispatch` action allows you to dispatch a Livewire event directly in the browser. Below is an example of a button that, when clicked, will dispatch the `post-deleted` event:

```blade
<button type="submit" wire:click="$dispatch('post-deleted')">Delete Post</button>
```

### `$event`

The `$event` action may be used within event listeners like `wire:click`. This action gives you access to the actual JavaScript event that was triggered, allowing you to reference the triggering element and other relevant information:

```blade
<input type="text" wire:keydown.enter="search($event.target.value)">
```

When the enter key is pressed while a user is typing in the input above, the contents of the input will be passed as a parameter to the `search()` action.

> For more information about magic actions, see [Javascript reference](/docs/4.x/javascript#the-wire-object-1)

### Using magic actions from Alpine

You can also call magic actions from Alpine using the `$wire` object. For example, you may use the `$wire` object to invoke the `$refresh` magic action:

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

---

## Wire Transition Reference
Wire Transition | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/wire-transition

## Reference

```blade
wire:transition="name"
```

| Expression | Description |
|------------|-------------|
| (none) | Uses `match-element` as the view-transition-name |
| `"name"` | Uses the provided string as the view-transition-name |

This directive has no modifiers.

---

## Wire Transition Respecting reduced motion
Wire Transition | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/wire-transition

## Respecting reduced motion

Livewire automatically respects the user's [prefers-reduced-motion](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@media/prefers-reduced-motion#user_preferences) setting. When enabled, transitions are disabled to avoid causing discomfort for users who are sensitive to motion.

---

## Wire Transition
Wire Transition | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/wire-transition

`wire:transition` enables smooth animations when elements appear, disappear, or change using the browser's native [View Transitions API](https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API).

Unlike JavaScript-based animation libraries, View Transitions are hardware-accelerated and handled natively by the browser, resulting in smoother animations with less overhead.

---

## Wire Click Parallel execution
Wire Click | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/wire-click

## Parallel execution

By default, Livewire queues actions within the same component. Use `.async` to allow actions to run in parallel:

```blade
<button wire:click.async="process">Process</button>
```

---

## Wire Transition Browser support
Wire Transition | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/wire-transition

## Browser support

View Transitions are supported in Chrome 111+, Edge 111+, and Safari 18+. In browsers that don't support View Transitions, elements will appear and disappear without animation—the functionality still works, just without the visual transition.

> [!warning] Firefox has limited support
> Firefox 144+ supports basic view transitions, but does not support transition types.

[View browser support on caniuse.com →](https://caniuse.com/view-transitions)

---

## Wire Transition Transition types
Wire Transition | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/wire-transition

## Transition types

For more complex scenarios like step wizards where you need different animations based on direction, you can use transition types. This allows you to animate "forward" and "backward" differently.

Use the `$this->transition()` method to set a transition type:

```php
class Wizard extends Component
{
    public $step = 1;

    public function goToStep($step)
    {
        $this->transition(type: $step > $this->step ? 'forward' : 'backward');

        $this->step = $step;
    }
}
```

Then target the type in your CSS using the `:active-view-transition-type()` selector:

```css
html:active-view-transition-type(forward) {
    &::view-transition-old(content) {
        animation: 300ms ease-out both slide-out-left;
    }
    &::view-transition-new(content) {
        animation: 300ms ease-in both slide-in-right;
    }
}

html:active-view-transition-type(backward) {
    &::view-transition-old(content) {
        animation: 300ms ease-out both slide-out-right;
    }
    &::view-transition-new(content) {
        animation: 300ms ease-in both slide-in-left;
    }
}

@keyframes slide-out-left {
    from { transform: translateX(0); opacity: 1; }
    to { transform: translateX(-100%); opacity: 0; }
}

@keyframes slide-in-right {
    from { transform: translateX(100%); opacity: 0; }
    to { transform: translateX(0); opacity: 1; }
}

@keyframes slide-out-right {
    from { transform: translateX(0); opacity: 1; }
    to { transform: translateX(100%); opacity: 0; }
}

@keyframes slide-in-left {
    from { transform: translateX(-100%); opacity: 0; }
    to { transform: translateX(0); opacity: 1; }
}
```

For methods that always transition in the same direction, you can use the `#[Transition]` attribute instead:

```php
use Livewire\Attributes\Transition;

class Wizard extends Component
{
    public $step = 1;

    #[Transition(type: 'forward')]
    public function next()
    {
        $this->step++;
    }

    #[Transition(type: 'backward')]
    public function previous()
    {
        $this->step--;
    }
}
```

### Unnamed transitions during a typed swap

When a typed transition is active, Livewire treats the swap as a single orchestrated unit. Any *unnamed* `wire:transition` element inside that swap stays unnamed and rides along with its parent's snapshot, instead of becoming an independent group with the browser's default fade.

```blade
<div wire:transition="slide">
    <p>Step content</p>

    {{-- Unnamed: rides along with the parent's "slide" during a typed swap --}}
    <div wire:transition>
        <button>Save</button>
    </div>
</div>
```

If you need an inner element to animate independently during a typed swap, give it an explicit name:

```blade
<div wire:transition="badge">...</div>
```

Outside of a typed transition (a regular morph, like a validation error appearing), unnamed `wire:transition` elements continue to use `match-element` and animate independently as before.

_[Truncated]_


output:
Wall time: 2.5270 seconds
Output:
[{"type":"text","text":"# Search Results\n\n## Wire Sort\nWire Sort | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/wire-sort\n\nLivewire provides drag-and-drop sorting through the `wire:sort` directive. Add it to a parent element and use `wire:sort:item` on each child to make lists sortable with smooth animations out of the box.\n\n---\n\n## Wire Sort Basic usage\nWire Sort | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/wire-sort\n\n## Basic usage\n\nTo make a list sortable, add `wire:sort` to the parent element with a handler method name, and `wire:sort:item` to each child with a unique identifier:\n\n```php\n<?php\n\nuse Livewire\\Component;\n\nnew class extends Component {\n    public TodoList $list;\n\n    public function handleSort($id, $position)\n    {\n        $task = $this->list->tasks()->findOrFail($id);\n\n        // Update the task's position and re-order other tasks...\n    }\n};\n```\n\n```blade\n<ul wire:sort=\"handleSort\">\n    @foreach ($list->tasks as $task)\n        <li wire:key=\"{{ $task->id }}\" wire:sort:item=\"{{ $task->id }}\">\n            {{ $task->title }}\n        </li>\n    @endforeach\n</ul>\n```\n\nWhen a user drags and drops an item to a new position, Livewire will call your handler with two parameters: the item's identifier (from `wire:sort:item`) and the new zero-based position.\n\nYou are responsible for persisting the new order in your database.\n\n---\n\n## Wire Sort Sorting across groups\nWire Sort | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/wire-sort\n\n## Sorting across groups\n\nTo allow dragging items between multiple lists, use `wire:sort:group` with the same group name on each container.\n\nTo identify which group the item was dropped into, add `wire:sort:group-id` to each container. Its value will be passed as a third parameter to your handler:\n\n```php\n<?php\n\nuse Livewire\\Component;\nuse Livewire\\Attributes\\Computed;\nuse App\\Models\\Card;\n\nnew class extends Component {\n    public Board $board;\n\n    #[Computed]\n    public function columns()\n    {\n        return $this->board->columns;\n    }\n\n    public function handleSort($id, $position, $columnId)\n    {\n        $card = $this->board->cards()->findOrFail($id);\n\n        // Update the card's position and re-order other cards...\n    }\n};\n```\n\n```blade\n<div>\n    @foreach ($this->columns as $column)\n        <ul wire:sort=\"handleSort\" wire:sort:group=\"cards\" wire:sort:group-id=\"{{ $column->id }}\">\n            @foreach ($column->cards as $card)\n                <li wire:key=\"{{ $card->id }}\" wire:sort:item=\"{{ $card->id }}\">\n                    {{ $card->title }}\n                </li>\n            @endforeach\n        </ul>\n    @endforeach\n</div>\n```\n\nWhen an item is dragged to a different group, only the destination group's handler fires.\n\n---\n\n## Wire Sort Reference\nWire Sort | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/wire-sort\n\n## Reference\n\n```blade\nwire:sort=\"method\"\nwire:sort:item=\"id\"\nwire:sort:group=\"name\"\nwire:sort:group-id=\"identifier\"\nwire:sort:handle\nwire:sort:ignore\n```\n\nThis directive has no modifiers.\n\n---\n\n## Wire Sort Sort handles\nWire Sort | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/wire-sort\n\n## Sort handles\n\nBy default, users can drag an item by clicking anywhere on it. To restrict dragging to a specific handle, use `wire:sort:handle`:\n\n```blade\n<ul wire:sort=\"handleSort\">\n    @foreach ($list->tasks as $task)\n        <li wire:key=\"{{ $task->id }}\" wire:sort:item=\"{{ $task->id }}\">\n            <div wire:sort:handle>\n                <!-- Drag icon... -->\n            </div>\n\n            {{ $task->title }}\n        </li>\n    @endforeach\n</ul>\n```\n\nNow users can only initiate a drag from the handle element.\n\n---\n\n## Wire Sort Ignoring elements\nWire Sort | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/wire-sort\n\n## Ignoring elements\n\nTo prevent specific areas from triggering drag operations, use `wire:sort:ignore`. This is useful for buttons or other interactive elements inside sortable items:\n\n```blade\n<ul wire:sort=\"handleSort\">\n    @foreach ($list->tasks as $task)\n        <li wire:key=\"{{ $task->id }}\" wire:sort:item=\"{{ $task->id }}\">\n            {{ $task->title }}\n\n            <div wire:sort:ignore>\n                <button type=\"button\">Edit</button>\n            </div>\n        </li>\n    @endforeach\n</ul>\n```\n\n---\n\n## Undocumented Features Todo Existing Docs to Update\nUndocumented Features Todo | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/undocumented-features-todo\n\n## Existing Docs to Update\n\n- **lazy.md** - Already updated with defer and bundle features\n- **actions.md** - Already updated with async actions\n- **islands.md** - Already updated with renderIsland/streamIsland\n- **components.md** - Already updated with convert command\n- **wire-sort.md** - Already created and documented\n\n---\n\n## Wire Confirm\nWire Confirm | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/wire-confirm\n\nBefore performing dangerous actions in Livewire, you may want to provide your users with some sort of visual confirmation.\n\nLivewire makes this easy to do by adding `wire:confirm` in addition to any action (`wire:click`, `wire:submit`, etc.).\n\nHere's an example of adding a confirmation dialog to a \"Delete post\" button:\n\n```blade\n<button\n    type=\"button\"\n    wire:click=\"delete\"\n    wire:confirm=\"Are you sure you want to delete this post?\"\n>\n    Delete post <!-- [tl! highlight:-2,1] -->\n</button>\n```\n\nWhen a user clicks \"Delete post\", Livewire will trigger a confirmation dialog (The default browser confirmation alert). If the user hits escape or presses cancel, the action won't be performed. If they press \"OK\", the action will be completed.\n\n---\n\n## Wire Transition See also\nWire Transition | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/wire-transition\n\n## See also\n\n- **[wire:show](/docs/4.x/wire-show)** — Toggle visibility with CSS display\n- **[Loading States](/docs/4.x/loading-states)** — Show loading indicators during requests\n- **[Alpine Transitions](https://alpinejs.dev/directives/transition)** — For more complex animation needs\n\n---\n\n## Upgrading New features in v4\nUpgrading | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/upgrading\n\n## New features in v4\n\nLivewire v4 introduces several powerful new features you can start using immediately:\n\n### Component features\n\n**Single-file and multi-file components**\n\nv4 introduces new component formats alongside the traditional class-based approach. Single-file components combine PHP and Blade in one file, while multi-file components organize PHP, Blade, JavaScript, and tests in a directory.\n\nBy default, view-based component files are prefixed with a ⚡ emoji to distinguish them from regular Blade files in your editor and searches. This can be disabled via the `make_command.emoji` config.\n\n```bash\nphp artisan make:livewire create-post        # Single-file (default)\nphp artisan make:livewire create-post --mfc  # Multi-file\nphp artisan livewire:convert create-post     # Convert between formats\n```\n\n[Learn more about component formats →](/docs/4.x/components)\n\n**Slots and attribute forwarding**\n\nComponents now support slots and automatic attribute bag forwarding using `{{ $attributes }}`, making component composition more flexible.\n\n[Learn more about nesting components →](/docs/4.x/nesting)\n\n**JavaScript in view-based components**\n\nView-based components can now include `<script>` tags without the `@script` wrapper. These scripts are served as separate cached files for better performance and automatic `$wire` binding:\n\n```blade\n<div>\n    <!-- Your component template -->\n</div>\n\n<script>\n    // $wire is automatically bound as 'this'\n    this.count++  // Same as $wire.count++\n\n    // $wire is still available if preferred\n    $wire.save()\n</script>\n```\n\n[Learn more about JavaScript in components →](/docs/4.x/javascript)\n\n### Islands\n\nIslands allow you to create isolated regions within a component that update independently, dramatically improving performance without creating separate child components.\n\n```blade\n@island(name: 'stats', lazy: true)\n    <div>{{ $this->expensiveStats }}</div>\n@endisland\n```\n\n[Learn more about islands →](/docs/4.x/islands)\n\n### Loading improvements\n\n**Deferred loading**\n\nIn addition to lazy loading (viewport-based), components can now be deferred to load immediately after the initial page load:\n\n```blade\n<livewire:revenue defer />\n```\n\n```php\n#[Defer]\nclass Revenue extends Component { ... }\n```\n\n**Bundled loading**\n\nControl whether multiple lazy/deferred components load in parallel or bundled together:\n\n```blade\n<livewire:revenue lazy.bundle />\n<livewire:expenses defer.bundle />\n```\n\n```php\n#[Lazy(bundle: true)]\nclass Revenue extends Component { ... }\n```\n\n[Learn more about lazy and deferred loading →](/docs/4.x/lazy)\n\n### Async actions\n\nRun actions in parallel without blocking other requests using the `.async` modifier or `#[Async]` attribute:\n\n```blade\n<button wire:click.async=\"logActivity\">Track</button>\n```\n\n```php\n#[Async]\npublic function logActivity() { ... }\n```\n\n[Learn more about async actions →](/docs/4.x/actions#parallel-execution-with-async)\n\n### New directives and modifiers\n\n**`wire:sort` - Drag-and-drop sorting**\n\nBuilt-in support for sortable lists with drag-and-drop:\n\n```blade\n<ul wire:sort=\"updateOrder\">\n    @foreach ($items as $item)\n        <li wire:sort:item=\"{{ $item->id }}\" wire:key=\"{{ $item->id }}\">{{ $item->name }}</li>\n    @endforeach\n</ul>\n```\n\n[Learn more about wire:sort →](/docs/4.x/wire-sort)\n\n**`wire:intersect` - Viewport intersection**\n\nRun actions when elements enter or leave the viewport, similar to Alpine's [`x-intersect`](https://alpinejs.dev/plugins/intersect):\n\n```blade\n<!-- Basic usage -->\n<div wire:intersect=\"loadMore\">...</div>\n\n<!-- With modifiers -->\n<div wire:intersect.once=\"trackView\">...</div>\n<div wire:intersect:leave=\"pauseVideo\">...</div>\n<div wire:intersect.half=\"loadMore\">...</div>\n<div wire:intersect.full=\"startAnimation\">...</div>\n\n<!-- With options -->\n<div wire:intersect.margin.200px=\"loadMore\">...</div>\n<div wire:intersect.threshold.50=\"trackScroll\">...</div>\n```\n\nAvailable modifiers:\n- `.once` - Fire only once\n- `.half` - Wait until half is visible\n- `.full` - Wait until fully visible\n- `.threshold.X` - Custom visibility percentage (0-100)\n- `.margin.Xpx` or `.margin.X%` - Intersection margin\n\n[Learn more about wire:intersect →](/docs/4.x/wire-intersect)\n\n**`wire:ref` - Element references**\n\nEasily reference and interact with elements in your template:\n\n```blade\n<div wire:ref=\"modal\">\n    <!-- Modal content -->\n</div>\n\n<button wire:click=\"$js.scrollToModal\">Scroll to modal</button>\n\n<script>\n    this.$js.scrollToModal = () => {\n        this.$refs.modal.scrollIntoView()\n    }\n</script>\n```\n\n[Learn more about wire:ref →](/docs/4.x/wire-ref)\n\n**`.renderless` modifier**\n\nSkip component re-rendering directly from the template:\n\n```blade\n<button wire:click.renderless=\"trackClick\">Track</button>\n```\n\nThis is an alternative to the `#[Renderless]` attribute for actions that don't need to update the UI.\n\n**`.preserve-scroll` modifier**\n\nPreserve scroll position during updates to prevent layout jumps:\n\n```blade\n<button wire:click.preserve-scroll=\"loadMore\">Load More</button>\n```\n\n**`data-loading` attribute**\n\nEvery element that triggers a network request automatically receives a `data-loading` attribute, making it easy to style loading states with Tailwind:\n\n```blade\n<button wire:click=\"save\" class=\"data-loading:opacity-50 data-loading:pointer-events-none\">\n    Save Changes\n</button>\n```\n\n[Learn more about loading states →](/docs/4.x/loading-states)\n\n### JavaScript improvements\n\n**`$errors` magic property**\n\nAccess your component's error bag from JavaScript:\n\n```blade\n<div wire:show=\"$errors.has('email')\">\n    <span wire:text=\"$errors.first('email')\"></span>\n</div>\n```\n\n[Learn more about validation →](/docs/4.x/validation)\n\n**`$intercept` magic**\n\nIntercept and modify Livewire requests from JavaScript:\n\n```blade\n<script>\nthis.$intercept('save', ({ ... }) => {\n    // ...\n})\n</script>\n```\n\n[Learn more about JavaScript interceptors →](/docs/4.x/javascript#interceptors)\n\n**Island targeting from JavaScript**\n\nTrigger island renders directly from the template:\n\n```blade\n<button wire:click=\"loadMore\" wire:island.append=\"stats\">\n    Load more\n</button>\n```\n\n[Learn more about islands →](/docs/4.x/islands)\n\n---\n\n## Upgrade Guide Scratch File new features (link to relevant docs files for each of these of course)\nUpgrade Guide Scratch File | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/upgrade-guide-scratch-file\n\n## new features (link to relevant docs files for each of these of course)\n\nnot sure where to note but view-based component files are prefixed withemojis by deatult. you can change make_command config for this and never see the emojis at all. however they are good to:\n- distinguish plain blade files from livewire view-based files in your filesystem and editor searching and what now.\n\n- data-loading attribute gets added to every element that triggered a network request. not sure if this is documented.\n- wire:[click|...].preserve-scroll=\"...\" - this helper makes it so that before a request comes back and updates the html fo the page it preserves scroll position so there's no layout jump while scrolling or anything. not sure if docuemnted. might need to look at source to determine where it goes\n- `wire:intersect`. this is now a new api to mirror alpine's x-intersect. it allows you to run an action when an element enters the viewport. `wire:intersect:enter|leave` are avaialble modifier type things. also things like .once (only fire once) .half (wait till half is vislble/invisible) .full (waits till all of it is inthe viewport to fire) .threshold.50 allows you to manually configure the amoyunt visible needed. margin.200px (allows you to set a margin of intersection) or all of them like so: margin.10%.25px.25.25px\n- SLots. components now have slots and attribute bag forwarding {{ $attributes }}\n- `wire:ref` allows you to easily access element from js and dispatch events to targeted components inside a loop or something\n- `wire:sort` new API to allow drag-sorting of items in a group. suports nesting and drag-between-groups type use-cases\n- Livewire now ships with an `$errors` magic that gives you client-side access to the compoennts's error bag (this might need to be documented somewhere actually)\n- Islands. obviously this is like the big feature of V4. there are docs for it.\n- wire:click=\"$island('foo', { mode: 'prepend' })\" i think is an undocumented feature for directly calling the render of an island and optionally pass a render mode like append or something\n- `$intercept` magic. like `$wire.$intercept()` or `this.$intercept()` optionally accepts name of action to intercept and callback with hooks as params. this is doucmented you can reference that.\n- `.async` wire:??? modifier and #[Async] attribute.\n- `.renderless` is now an avaialble modifier like .async as an alternative to the existing #[Renderless] action/method attribute\n- CSP-mode. If you set `csp_safe` to true in your config livewire will now use the alpine CSP build and your whole app will avoid unsafe eval violations. there are many restrictions when using this mode. you can no longer put complex js or global expressions like: `wire:click=\"doSomething($event.detail.foo)\"` or global things like `wire:click=\"redirectHere(window.location.href)\"`\n- defer loading in addition to lazy. <livewire:component defer> attribute #[Defer] attribute for class (same signatures as lazy)\n- can use additional `lazy.bundle` or `defer.bundle` additional syntax to flag the lazy load to bundle multiple lazy loads instead of them being isolated/parralel. There's also `#[Defer|Lazy(bundle: true)]` for this.\n-kinda big one. view-based components can have <script></script> at the bottom with now `@script` wrapper and they work differently. their source is made available via a faux js file to frontend so caching and native module code can work. also they automatically bind this. ($wire is what this is ) and $wire is still available but this. is preferred.\n\n---\n\n## Wire Model Reference\nWire Model | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/wire-model\n\n## Reference\n\n```blade\nwire:model=\"propertyName\"\nwire:model=\"property.nested\"\nwire:model=\"property['nested']\"\nwire:model=\"property[0]\"\n```\n\n### Modifiers\n\n| Modifier | Description |\n|----------|-------------|\n| `.live` | Send updates to the server |\n| `.blur` | Only update on blur |\n| `.change` | Only update on change |\n| `.enter` | Only update on enter key |\n| `.lazy` | Update on change and send network request (v3 compatible) |\n| `.debounce.Xms` | Debounce updates (use with `.live`) |\n| `.throttle.Xms` | Throttle updates (use with `.live`) |\n| `.number` | Cast value to `int` on the server |\n| `.boolean` | Cast value to `bool` on the server |\n| `.fill` | Use initial value from HTML `value` attribute |\n| `.deep` | Also listen to events from child elements |\n| `.preserve-scroll` | Maintain scroll position during updates |\n\n---\n\n## Actions Magic actions\nActions | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/actions\n\n## Magic actions\n\nLivewire provides a set of \"magic\" actions that allow you to perform common tasks in your components without defining custom methods. These magic actions can be used within event listeners defined in your Blade templates.\n\n### `$parent`\n\nThe `$parent` magic variable allows you to access parent component properties and call parent component actions from a child component:\n\n```blade\n<button wire:click=\"$parent.removePost({{ $post->id }})\">Remove</button>\n```\n\nIn the above example, if a parent component has a `removePost()` action, a child can call it directly from its Blade template using `$parent.removePost()`.\n\n### `$set`\n\nThe `$set` magic action allows you to update a property in your Livewire component directly from the Blade template. To use `$set`, provide the property you want to update and the new value as arguments:\n\n```blade\n<button wire:click=\"$set('query', '')\">Reset Search</button>\n```\n\nIn this example, when the button is clicked, a network request is dispatched that sets the `$query` property in the component to `''`.\n\n### `$refresh`\n\nThe `$refresh` action triggers a re-render of your Livewire component. This can be useful when updating the component's view without changing any property values:\n\n```blade\n<button wire:click=\"$refresh\">Refresh</button>\n```\n\nWhen the button is clicked, the component will re-render, allowing you to see the latest changes in the view.\n\n### `$toggle`\n\nThe `$toggle` action is used to toggle the value of a boolean property in your Livewire component:\n\n```blade\n<button wire:click=\"$toggle('sortAsc')\">\n    Sort {{ $sortAsc ? 'Descending' : 'Ascending' }}\n</button>\n```\n\nIn this example, when the button is clicked, the `$sortAsc` property in the component will toggle between `true` and `false`.\n\n### `$dispatch`\n\nThe `$dispatch` action allows you to dispatch a Livewire event directly in the browser. Below is an example of a button that, when clicked, will dispatch the `post-deleted` event:\n\n```blade\n<button type=\"submit\" wire:click=\"$dispatch('post-deleted')\">Delete Post</button>\n```\n\n### `$event`\n\nThe `$event` action may be used within event listeners like `wire:click`. This action gives you access to the actual JavaScript event that was triggered, allowing you to reference the triggering element and other relevant information:\n\n```blade\n<input type=\"text\" wire:keydown.enter=\"search($event.target.value)\">\n```\n\nWhen the enter key is pressed while a user is typing in the input above, the contents of the input will be passed as a parameter to the `search()` action.\n\n> For more information about magic actions, see [Javascript reference](/docs/4.x/javascript#the-wire-object-1)\n\n### Using magic actions from Alpine\n\nYou can also call magic actions from Alpine using the `$wire` object. For example, you may use the `$wire` object to invoke the `$refresh` magic action:\n\n```blade\n<button x-on:click=\"$wire.$refresh()\">Refresh</button>\n```\n\n---\n\n## Wire Transition Reference\nWire Transition | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/wire-transition\n\n## Reference\n\n```blade\nwire:transition=\"name\"\n```\n\n| Expression | Description |\n|------------|-------------|\n| (none) | Uses `match-element` as the view-transition-name |\n| `\"name\"` | Uses the provided string as the view-transition-name |\n\nThis directive has no modifiers.\n\n---\n\n## Wire Transition Respecting reduced motion\nWire Transition | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/wire-transition\n\n## Respecting reduced motion\n\nLivewire automatically respects the user's [prefers-reduced-motion](https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/At-rules/@media/prefers-reduced-motion#user_preferences) setting. When enabled, transitions are disabled to avoid causing discomfort for users who are sensitive to motion.\n\n---\n\n## Wire Transition\nWire Transition | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/wire-transition\n\n`wire:transition` enables smooth animations when elements appear, disappear, or change using the browser's native [View Transitions API](https://developer.mozilla.org/en-US/docs/Web/API/View_Transitions_API).\n\nUnlike JavaScript-based animation libraries, View Transitions are hardware-accelerated and handled natively by the browser, resulting in smoother animations with less overhead.\n\n---\n\n## Wire Click Parallel execution\nWire Click | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/wire-click\n\n## Parallel execution\n\nBy default, Livewire queues actions within the same component. Use `.async` to allow actions to run in parallel:\n\n```blade\n<button wire:click.async=\"process\">Process</button>\n```\n\n---\n\n## Wire Transition Browser support\nWire Transition | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/wire-transition\n\n## Browser support\n\nView Transitions are supported in Chrome 111+, Edge 111+, and Safari 18+. In browsers that don't support View Transitions, elements will appear and disappear without animation—the functionality still works, just without the visual transition.\n\n> [!warning] Firefox has limited support\n> Firefox 144+ supports basic view transitions, but does not support transition types.\n\n[View browser support on caniuse.com →](https://caniuse.com/view-transitions)\n\n---\n\n## Wire Transition Transition types\nWire Transition | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/wire-transition\n\n## Transition types\n\nFor more complex scenarios like step wizards where you need different animations based on direction, you can use transition types. This allows you to animate \"forward\" and \"backward\" differently.\n\nUse the `$this->transition()` method to set a transition type:\n\n```php\nclass Wizard extends Component\n{\n    public $step = 1;\n\n    public function goToStep($step)\n    {\n        $this->transition(type: $step > $this->step ? 'forward' : 'backward');\n\n        $this->step = $step;\n    }\n}\n```\n\nThen target the type in your CSS using the `:active-view-transition-type()` selector:\n\n```css\nhtml:active-view-transition-type(forward) {\n    &::view-transition-old(content) {\n        animation: 300ms ease-out both slide-out-left;\n    }\n    &::view-transition-new(content) {\n        animation: 300ms ease-in both slide-in-right;\n    }\n}\n\nhtml:active-view-transition-type(backward) {\n    &::view-transition-old(content) {\n        animation: 300ms ease-out both slide-out-right;\n    }\n    &::view-transition-new(content) {\n        animation: 300ms ease-in both slide-in-left;\n    }\n}\n\n@keyframes slide-out-left {\n    from { transform: translateX(0); opacity: 1; }\n    to { transform: translateX(-100%); opacity: 0; }\n}\n\n@keyframes slide-in-right {\n    from { transform: translateX(100%); opacity: 0; }\n    to { transform: translateX(0); opacity: 1; }\n}\n\n@keyframes slide-out-right {\n    from { transform: translateX(0); opacity: 1; }\n    to { transform: translateX(100%); opacity: 0; }\n}\n\n@keyframes slide-in-left {\n    from { transform: translateX(-100%); opacity: 0; }\n    to { transform: translateX(0); opacity: 1; }\n}\n```\n\nFor methods that always transition in the same direction, you can use the `#[Transition]` attribute instead:\n\n```php\nuse Livewire\\Attributes\\Transition;\n\nclass Wizard extends Component\n{\n    public $step = 1;\n\n    #[Transition(type: 'forward')]\n    public function next()\n    {\n        $this->step++;\n    }\n\n    #[Transition(type: 'backward')]\n    public function previous()\n    {\n        $this->step--;\n    }\n}\n```\n\n### Unnamed transitions during a typed swap\n\nWhen a typed transition is active, Livewire treats the swap as a single orchestrated unit. Any *unnamed* `wire:transition` element inside that swap stays unnamed and rides along with its parent's snapshot, instead of becoming an independent group with the browser's default fade.\n\n```blade\n<div wire:transition=\"slide\">\n    <p>Step content</p>\n\n    {{-- Unnamed: rides along with the parent's \"slide\" during a typed swap --}}\n    <div wire:transition>\n        <button>Save</button>\n    </div>\n</div>\n```\n\nIf you need an inner element to animate independently during a typed swap, give it an explicit name:\n\n```blade\n<div wire:transition=\"badge\">...</div>\n```\n\nOutside of a typed transition (a regular morph, like a validation error appearing), unnamed `wire:transition` elements continue to use `match-element` and animate independently as before.\n\n_[Truncated]_"}]


cmd: rg -n "navigation" tests/Feature tests/Unit -g '*.php' && sed -n '1,260p' tests/Feature/Admin/ContentManagementTest.php && sed -n '1,220p' tests/Feature/Storefront/ThemeDataTest.php