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

namespace App\Livewire\Storefront;

use App\Models\Cart;
use App\Models\CartLine;
use App\Models\Customer;
use App\Models\Store;
use App\Services\CartService;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Auth;
use Livewire\Attributes\On;
use Livewire\Component;

class CartDrawer extends Component
{
    public int $storeId;

    public function mount(): void
    {
        $this->storeId = $this->store()->getKey();
    }

    #[On('cart-updated')]
    public function refreshCart(): void {}

    public function increaseQuantity(int $lineId): void
    {
        $line = $this->cartLine($lineId);

        if (! $line instanceof CartLine) {
            return;
        }

        app(CartService::class)->updateLineQuantity($line->cart, $line->getKey(), $line->quantity + 1);
        $this->dispatch('cart-updated');
    }

    public function decreaseQuantity(int $lineId): void
    {
        $line = $this->cartLine($lineId);

        if (! $line instanceof CartLine) {
            return;
        }

        app(CartService::class)->updateLineQuantity($line->cart, $line->getKey(), $line->quantity - 1);
        $this->dispatch('cart-updated');
    }

    public function removeLine(int $lineId): void
    {
        $line = $this->cartLine($lineId);

        if (! $line instanceof CartLine) {
            return;
        }

        app(CartService::class)->removeLine($line->cart, $line->getKey());
        $this->dispatch('cart-updated');
    }

    public function checkout(): void
    {
        if ($this->lineCount() === 0) {
            return;
        }

        $this->redirectRoute('checkout.show', navigate: true);
    }

    public function store(): Store
    {
        if (isset($this->storeId)) {
            $store = Store::query()->findOrFail($this->storeId);
            app()->instance('current_store', $store);

            return $store;
        }

        $store = app('current_store');

        abort_unless($store instanceof Store, 404);

        return $store;
    }

    public function cart(): ?Cart
    {
        $cart = app(CartService::class)->currentForSession($this->store(), $this->customer());

        return $cart?->load([
            'lines.variant.product',
            'lines.variant.optionValues.option',
        ]);
    }

    /**
     * @return Collection<int, CartLine>
     */
    public function lines(): Collection
    {
        return $this->cart()?->lines->sortBy('id')->values() ?? collect();
    }

    public function lineCount(): int
    {
        return $this->lines()->sum('quantity');
    }

    public function subtotal(): int
    {
        return $this->lines()->sum('line_subtotal_amount');
    }

    public function render(): mixed
    {
        return view('livewire.storefront.cart-drawer', [
            'cart' => $this->cart(),
            'lines' => $this->lines(),
            'lineCount' => $this->lineCount(),
            'subtotal' => $this->subtotal(),
        ]);
    }

    private function customer(): ?Customer
    {
        $customer = Auth::guard('customer')->user();

        return $customer instanceof Customer ? $customer : null;
    }

    private function cartLine(int $lineId): ?CartLine
    {
        return $this->lines()->firstWhere('id', $lineId);
    }
}


cmd: sed -n '320,460p' app/Livewire/Storefront/Checkout/Show.php


command: sed -n '320,460p' app/Livewire/Storefront/Checkout/Show.php

Show.php

type: read

aggregated_output:
            'totals' => $this->totals(),
        ])->layout('layouts.storefront', [
            'title' => 'Checkout',
        ]);
    }

    private function customer(): ?Customer
    {
        $customer = Auth::guard('customer')->user();

        return $customer instanceof Customer ? $customer : null;
    }

    private function fillFromCheckout(?Checkout $checkout): void
    {
        if (! $checkout instanceof Checkout) {
            return;
        }

        $this->email = $checkout->email ?: $this->email;
        $this->shippingAddress = array_replace($this->shippingAddress, $checkout->shipping_address_json ?? []);
        $this->billingAddress = array_replace($this->billingAddress, $checkout->billing_address_json ?? []);
        $this->selectedShippingRateId = $checkout->shipping_method_id;
        $this->discountCode = (string) ($checkout->discount_code ?? '');
        $this->paymentMethod = (string) ($checkout->payment_method ?? $this->paymentMethod);
        $this->step = match ($checkout->status) {
            CheckoutStatus::Started => 'address',
            CheckoutStatus::Addressed => 'shipping',
            CheckoutStatus::ShippingSelected => 'payment',
            CheckoutStatus::PaymentSelected => 'reserved',
            default => 'address',
        };
    }
}

status: completed


output:
Chunk ID: a12da5
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 326
Output:
            'totals' => $this->totals(),
        ])->layout('layouts.storefront', [
            'title' => 'Checkout',
        ]);
    }

    private function customer(): ?Customer
    {
        $customer = Auth::guard('customer')->user();

        return $customer instanceof Customer ? $customer : null;
    }

    private function fillFromCheckout(?Checkout $checkout): void
    {
        if (! $checkout instanceof Checkout) {
            return;
        }

        $this->email = $checkout->email ?: $this->email;
        $this->shippingAddress = array_replace($this->shippingAddress, $checkout->shipping_address_json ?? []);
        $this->billingAddress = array_replace($this->billingAddress, $checkout->billing_address_json ?? []);
        $this->selectedShippingRateId = $checkout->shipping_method_id;
        $this->discountCode = (string) ($checkout->discount_code ?? '');
        $this->paymentMethod = (string) ($checkout->payment_method ?? $this->paymentMethod);
        $this->step = match ($checkout->status) {
            CheckoutStatus::Started => 'address',
            CheckoutStatus::Addressed => 'shipping',
            CheckoutStatus::ShippingSelected => 'payment',
            CheckoutStatus::PaymentSelected => 'reserved',
            default => 'address',
        };
    }
}


status: completed

input:
*** Begin Patch
*** Update File: /Users/fabianwesner/Herd/shop/app/Livewire/Storefront/Account/Auth/Login.php
@@
-        if ($customer instanceof Customer) {
+        if ($customer instanceof Customer && request()->session()->has('cart_id')) {
             app(CartService::class)->getOrCreateForSession(Store::query()->findOrFail($this->storeId), $customer);
         }
*** End Patch


stdout:
Success. Updated the following files:
M /Users/fabianwesner/Herd/shop/app/Livewire/Storefront/Account/Auth/Login.php

type: update

unified_diff:
@@ -70,3 +70,3 @@

-        if ($customer instanceof Customer) {
+        if ($customer instanceof Customer && request()->session()->has('cart_id')) {
             app(CartService::class)->getOrCreateForSession(Store::query()->findOrFail($this->storeId), $customer);


output: {"output":"Success. Updated the following files:\nM /Users/fabianwesner/Herd/shop/app/Livewire/Storefront/Account/Auth/Login.php\n","metadata":{"exit_code":0,"duration_seconds":0.0}}


message: I spotted a subtle login behavior: the cart merge hook would create an empty cart on every customer login. I changed it to run only when a guest cart exists and am adding a regression test for the login merge path.

phase: commentary


type: message

role: assistant

text: I spotted a subtle login behavior: the cart merge hook would create an empty cart on every customer login. I changed it to run only when a guest cart exists and am adding a regression test for the login merge path.


status: completed

input:
*** Begin Patch
*** Update File: /Users/fabianwesner/Herd/shop/tests/Feature/Storefront/CartCheckoutUiTest.php
@@
 use App\Enums\CheckoutStatus;
+use App\Enums\CartStatus;
+use App\Livewire\Storefront\Account\Auth\Login as CustomerLogin;
 use App\Livewire\Storefront\Cart\Show as CartShow;
 use App\Livewire\Storefront\Checkout\Show as CheckoutShow;
 use App\Livewire\Storefront\Products\Show as ProductShow;
 use App\Models\Cart;
 use App\Models\Checkout;
+use App\Models\Customer;
@@
 test('cart page updates and removes line quantities', function () {
@@
 });
+
+test('customer login merges an existing guest cart without creating empty carts', function () {
+    $store = storefrontUiStore();
+    $variant = storefrontUiVariant($store);
+    $customer = Customer::withoutGlobalScopes()
+        ->where('store_id', $store->getKey())
+        ->where('email', 'customer@acme.test')
+        ->firstOrFail();
+    $customerCart = app(CartService::class)->create($store, $customer);
+    $guestCart = app(CartService::class)->create($store);
+    session(['cart_id' => $guestCart->getKey()]);
+
+    app(CartService::class)->addLine($customerCart, $variant->getKey(), 1);
+    app(CartService::class)->addLine($guestCart, $variant->getKey(), 2);
+
+    Livewire::test(CustomerLogin::class)
+        ->set('email', 'customer@acme.test')
+        ->set('password', 'password')
+        ->call('login');
+
+    expect($guestCart->refresh()->status)->toBe(CartStatus::Abandoned)
+        ->and($customerCart->refresh()->lines()->withoutGlobalScopes()->where('variant_id', $variant->getKey())->first()?->quantity)->toBe(2)
+        ->and(Cart::withoutGlobalScopes()->where('store_id', $store->getKey())->count())->toBe(2);
+});

 test('checkout page progresses through address shipping discount and payment selection', function () {
*** End Patch


stdout:
Success. Updated the following files:
M /Users/fabianwesner/Herd/shop/tests/Feature/Storefront/CartCheckoutUiTest.php

unified_diff:
@@ -3,2 +3,4 @@
 use App\Enums\CheckoutStatus;
+use App\Enums\CartStatus;
+use App\Livewire\Storefront\Account\Auth\Login as CustomerLogin;
 use App\Livewire\Storefront\Cart\Show as CartShow;
@@ -8,2 +10,3 @@
 use App\Models\Checkout;
+use App\Models\Customer;
 use App\Models\InventoryItem;
@@ -82,2 +85,26 @@

+test('customer login merges an existing guest cart without creating empty carts', function () {
+    $store = storefrontUiStore();
+    $variant = storefrontUiVariant($store);
+    $customer = Customer::withoutGlobalScopes()
+        ->where('store_id', $store->getKey())
+        ->where('email', 'customer@acme.test')
+        ->firstOrFail();
+    $customerCart = app(CartService::class)->create($store, $customer);
+    $guestCart = app(CartService::class)->create($store);
+    session(['cart_id' => $guestCart->getKey()]);
+
+    app(CartService::class)->addLine($customerCart, $variant->getKey(), 1);
+    app(CartService::class)->addLine($guestCart, $variant->getKey(), 2);
+
+    Livewire::test(CustomerLogin::class)
+        ->set('email', 'customer@acme.test')
+        ->set('password', 'password')
+        ->call('login');
+
+    expect($guestCart->refresh()->status)->toBe(CartStatus::Abandoned)
+        ->and($customerCart->refresh()->lines()->withoutGlobalScopes()->where('variant_id', $variant->getKey())->first()?->quantity)->toBe(2)
+        ->and(Cart::withoutGlobalScopes()->where('store_id', $store->getKey())->count())->toBe(2);
+});
+
 test('checkout page progresses through address shipping discount and payment selection', function () {


output: {"output":"Success. Updated the following files:\nM /Users/fabianwesner/Herd/shop/tests/Feature/Storefront/CartCheckoutUiTest.php\n","metadata":{"exit_code":0,"duration_seconds":0.0}}


output:
Chunk ID: c5c47c
Wall time: 0.2422 seconds
Process exited with code 0
Original token count: 30
Output:
{"result":"fixed","files":[{"path":"tests\/Feature\/Storefront\/CartCheckoutUiTest.php","fixers":["ordered_imports"]}]}


command:
/bin/zsh
-lc
vendor/bin/pint --dirty --format agent

type: unknown

cmd: vendor/bin/pint --dirty --format agent

result: fixed


cmd: php artisan test --compact tests/Feature/Storefront/CartCheckoutUiTest.php


output:
Chunk ID: 3d5962
Wall time: 2.1296 seconds
Process exited with code 2
Original token count: 249
Output:

  ..⨯.
  ────────────────────────────────────────────────────────────────────────────
   FAILED  Tests\Feature\Storefront\CartCheckoutUiTest > c…  RuntimeException
  Session store not set on request.

  at vendor/laravel/framework/src/Illuminate/Http/Request.php:610
    606▕      */
    607▕     public function session()
    608▕     {
    609▕         if (! $this->hasSession()) {
  ➜ 610▕             throw new RuntimeException('Session store not set on request.');
    611▕         }
    612▕
    613▕         return $this->session->store;
    614▕     }

      +1 vendor frames
  2   app/Livewire/Storefront/Account/Auth/Login.php:71
      +32 vendor frames
  35  tests/Feature/Storefront/CartCheckoutUiTest.php:103

  Tests:    1 failed, 3 passed (16 assertions)
  Duration: 1.85s


command: php artisan test --compact tests/Feature/Storefront/CartCheckoutUiTest.php

aggregated_output:
  ..⨯.
  ────────────────────────────────────────────────────────────────────────────
   FAILED  Tests\Feature\Storefront\CartCheckoutUiTest > c…  RuntimeException
  Session store not set on request.

  at vendor/laravel/framework/src/Illuminate/Http/Request.php:610
    606▕      */
    607▕     public function session()
    608▕     {
    609▕         if (! $this->hasSession()) {
  ➜ 610▕             throw new RuntimeException('Session store not set on request.');
    611▕         }
    612▕
    613▕         return $this->session->store;
    614▕     }

      +1 vendor frames
  2   app/Livewire/Storefront/Account/Auth/Login.php:71
      +32 vendor frames
  35  tests/Feature/Storefront/CartCheckoutUiTest.php:103

  Tests:    1 failed, 3 passed (16 assertions)
  Duration: 1.85s

status: failed


status: completed

input:
*** Begin Patch
*** Update File: /Users/fabianwesner/Herd/shop/app/Livewire/Storefront/Account/Auth/Login.php
@@
-        if ($customer instanceof Customer && request()->session()->has('cart_id')) {
+        if ($customer instanceof Customer && request()->hasSession() && request()->session()->has('cart_id')) {
             app(CartService::class)->getOrCreateForSession(Store::query()->findOrFail($this->storeId), $customer);
         }
*** End Patch


unified_diff:
@@ -70,3 +70,3 @@

-        if ($customer instanceof Customer && request()->session()->has('cart_id')) {
+        if ($customer instanceof Customer && request()->hasSession() && request()->session()->has('cart_id')) {
             app(CartService::class)->getOrCreateForSession(Store::query()->findOrFail($this->storeId), $customer);


output: {"output":"Success. Updated the following files:\nM /Users/fabianwesner/Herd/shop/app/Livewire/Storefront/Account/Auth/Login.php\n","metadata":{"exit_code":0,"duration_seconds":0.0}}


status: completed

input:
*** Begin Patch
*** Update File: /Users/fabianwesner/Herd/shop/tests/Feature/Storefront/CartCheckoutUiTest.php
@@
-    Livewire::test(CustomerLogin::class)
+    Livewire::withSession(['cart_id' => $guestCart->getKey()])
+        ->test(CustomerLogin::class)
         ->set('email', 'customer@acme.test')
         ->set('password', 'password')
         ->call('login');
*** End Patch


unified_diff:
@@ -99,3 +99,4 @@

-    Livewire::test(CustomerLogin::class)
+    Livewire::withSession(['cart_id' => $guestCart->getKey()])
+        ->test(CustomerLogin::class)
         ->set('email', 'customer@acme.test')


output: {"output":"Success. Updated the following files:\nM /Users/fabianwesner/Herd/shop/tests/Feature/Storefront/CartCheckoutUiTest.php\n","metadata":{"exit_code":0,"duration_seconds":0.0}}


output:
Chunk ID: a78055
Wall time: 0.2470 seconds
Process exited with code 0
Original token count: 5
Output:
{"result":"pass"}


result: pass


output:
Chunk ID: 5a572e
Wall time: 2.0661 seconds
Process exited with code 2
Original token count: 222
Output:

  ..⨯.
  ────────────────────────────────────────────────────────────────────────────
   FAILED  Tests\Feature\Storefront\CartCheckoutUiTest > customer log…  Error
  Call to undefined method Livewire\LivewireManager::withSession()

  at vendor/laravel/framework/src/Illuminate/Support/Facades/Facade.php:363
    359▕         if (! $instance) {
    360▕             throw new RuntimeException('A facade root has not been set.');
    361▕         }
    362▕
  ➜ 363▕         return $instance->$method(...$args);
    364▕     }
    365▕ }
    366▕

      +1 vendor frames
  2   tests/Feature/Storefront/CartCheckoutUiTest.php:100

  Tests:    1 failed, 3 passed (16 assertions)
  Duration: 1.81s


aggregated_output:
  ..⨯.
  ────────────────────────────────────────────────────────────────────────────
   FAILED  Tests\Feature\Storefront\CartCheckoutUiTest > customer log…  Error
  Call to undefined method Livewire\LivewireManager::withSession()

  at vendor/laravel/framework/src/Illuminate/Support/Facades/Facade.php:363
    359▕         if (! $instance) {
    360▕             throw new RuntimeException('A facade root has not been set.');
    361▕         }
    362▕
  ➜ 363▕         return $instance->$method(...$args);
    364▕     }
    365▕ }
    366▕

      +1 vendor frames
  2   tests/Feature/Storefront/CartCheckoutUiTest.php:100

  Tests:    1 failed, 3 passed (16 assertions)
  Duration: 1.81s


status: completed

input:
*** Begin Patch
*** Update File: /Users/fabianwesner/Herd/shop/app/Livewire/Storefront/Account/Auth/Login.php
@@
-        if ($customer instanceof Customer && request()->hasSession() && request()->session()->has('cart_id')) {
+        if ($customer instanceof Customer && session()->has('cart_id')) {
             app(CartService::class)->getOrCreateForSession(Store::query()->findOrFail($this->storeId), $customer);
         }
*** End Patch


unified_diff:
@@ -70,3 +70,3 @@

-        if ($customer instanceof Customer && request()->hasSession() && request()->session()->has('cart_id')) {
+        if ($customer instanceof Customer && session()->has('cart_id')) {
             app(CartService::class)->getOrCreateForSession(Store::query()->findOrFail($this->storeId), $customer);


output: {"output":"Success. Updated the following files:\nM /Users/fabianwesner/Herd/shop/app/Livewire/Storefront/Account/Auth/Login.php\n","metadata":{"exit_code":0,"duration_seconds":0.0}}


status: completed

input:
*** Begin Patch
*** Update File: /Users/fabianwesner/Herd/shop/tests/Feature/Storefront/CartCheckoutUiTest.php
@@
-    Livewire::withSession(['cart_id' => $guestCart->getKey()])
-        ->test(CustomerLogin::class)
+    Livewire::test(CustomerLogin::class)
         ->set('email', 'customer@acme.test')
         ->set('password', 'password')
         ->call('login');
*** End Patch


unified_diff:
@@ -99,4 +99,3 @@

-    Livewire::withSession(['cart_id' => $guestCart->getKey()])
-        ->test(CustomerLogin::class)
+    Livewire::test(CustomerLogin::class)
         ->set('email', 'customer@acme.test')


output: {"output":"Success. Updated the following files:\nM /Users/fabianwesner/Herd/shop/tests/Feature/Storefront/CartCheckoutUiTest.php\n","metadata":{"exit_code":0,"duration_seconds":0.0}}


output:
Chunk ID: 1155d1
Wall time: 2.0696 seconds
Process exited with code 0
Original token count: 17
Output:

  ....

  Tests:    4 passed (19 assertions)
  Duration: 1.85s


aggregated_output:
  ....

  Tests:    4 passed (19 assertions)
  Duration: 1.85s


message: The login merge regression test is green now. I’m rerunning the affected group and full suite with the final login/session behavior.


text: The login merge regression test is green now. I’m rerunning the affected group and full suite with the final login/session behavior.


cmd: php artisan test --compact tests/Feature/Storefront tests/Feature/Cart tests/Feature/Checkout tests/Feature/Catalog tests/Feature/Foundation/CustomerAuthTest.php


output:
Chunk ID: 61de0e
Wall time: 5.4185 seconds
Process exited with code 0
Original token count: 28
Output:

  ...............................................

  Tests:    47 passed (236 assertions)
  Duration: 5.34s


command: php artisan test --compact tests/Feature/Storefront tests/Feature/Cart tests/Feature/Checkout tests/Feature/Catalog tests/Feature/Foundation/CustomerAuthTest.php


output:
Chunk ID: f1da2a
Wall time: 8.8483 seconds
Process exited with code 0
Original token count: 39
Output:

  ............................................................................
  .............

  Tests:    89 passed (341 assertions)
  Duration: 8.57s


command:
/bin/zsh
-lc
php artisan test --compact

cmd: php artisan test --compact


status: completed

input:
*** Begin Patch
*** Update File: /Users/fabianwesner/Herd/shop/specs/progress.md
@@
-- 2026-05-03: `php artisan test --compact tests/Feature/Storefront/CartCheckoutUiTest.php` passed: 3 tests, 16 assertions.
-- 2026-05-03: `php artisan test --compact tests/Feature/Storefront tests/Feature/Cart tests/Feature/Checkout tests/Feature/Catalog` passed: 43 tests, 217 assertions.
+- 2026-05-03: `php artisan test --compact tests/Feature/Storefront/CartCheckoutUiTest.php` passed: 4 tests, 19 assertions.
+- 2026-05-03: `php artisan test --compact tests/Feature/Storefront tests/Feature/Cart tests/Feature/Checkout tests/Feature/Catalog tests/Feature/Foundation/CustomerAuthTest.php` passed: 47 tests, 236 assertions.
@@
-- 2026-05-03: `php artisan test --compact` passed: 88 tests, 338 assertions.
+- 2026-05-03: `php artisan test --compact` passed: 89 tests, 341 assertions.
*** End Patch


stdout:
Success. Updated the following files:
M /Users/fabianwesner/Herd/shop/specs/progress.md

unified_diff:
@@ -85,6 +85,6 @@
 - 2026-05-03: `php artisan route:list --path=cart` and `php artisan route:list --path=checkout` confirmed `/cart` and `/checkout` Livewire routes.
-- 2026-05-03: `php artisan test --compact tests/Feature/Storefront/CartCheckoutUiTest.php` passed: 3 tests, 16 assertions.
-- 2026-05-03: `php artisan test --compact tests/Feature/Storefront tests/Feature/Cart tests/Feature/Checkout tests/Feature/Catalog` passed: 43 tests, 217 assertions.
+- 2026-05-03: `php artisan test --compact tests/Feature/Storefront/CartCheckoutUiTest.php` passed: 4 tests, 19 assertions.
+- 2026-05-03: `php artisan test --compact tests/Feature/Storefront tests/Feature/Cart tests/Feature/Checkout tests/Feature/Catalog tests/Feature/Foundation/CustomerAuthTest.php` passed: 47 tests, 236 assertions.
 - 2026-05-03: `vendor/bin/pint --dirty --format agent` passed after Phase 4 UI changes.
-- 2026-05-03: `php artisan test --compact` passed: 88 tests, 338 assertions.
+- 2026-05-03: `php artisan test --compact` passed: 89 tests, 341 assertions.
 - 2026-05-03: `php artisan migrate:fresh --seed --no-interaction` passed after the Phase 4 UI changes.


output: {"output":"Success. Updated the following files:\nM /Users/fabianwesner/Herd/shop/specs/progress.md\n","metadata":{"exit_code":0,"duration_seconds":0.0}}


output:
Chunk ID: 8e005e
Wall time: 0.2423 seconds
Process exited with code 0
Original token count: 5
Output:
{"result":"pass"}


cmd: php artisan migrate:fresh --seed --no-interaction


output:
Chunk ID: 1b8645
Wall time: 1.0734 seconds
Process exited with code 0
Original token count: 1491
Output:

  Dropping all tables ............................................ 6.12ms DONE

   INFO  Preparing database.

  Creating migration table ....................................... 1.97ms DONE

   INFO  Running migrations.

  0001_01_01_000000_create_users_table ........................... 1.91ms DONE
  0001_01_01_000001_create_cache_table ........................... 0.41ms DONE
  0001_01_01_000002_create_jobs_table ............................ 1.08ms DONE
  2025_08_14_170933_add_two_factor_columns_to_users_table ........ 0.46ms DONE
  2026_05_03_211409_create_organizations_table ................... 0.23ms DONE
  2026_05_03_211413_create_stores_table .......................... 6.72ms DONE
  2026_05_03_211416_create_store_domains_table ................... 0.42ms DONE
  2026_05_03_211419_create_store_settings_table .................. 0.22ms DONE
  2026_05_03_211425_create_store_users_table ..................... 0.36ms DONE
  2026_05_03_211556_create_customers_table ....................... 0.31ms DONE
  2026_05_03_212158_create_customer_password_reset_tokens_table .. 0.22ms DONE
  2026_05_03_213856_create_products_table ........................ 0.55ms DONE
  2026_05_03_213857_create_product_options_table ................. 0.29ms DONE
  2026_05_03_213858_create_product_option_values_table ........... 0.30ms DONE
  2026_05_03_213859_create_product_variants_table ................ 0.57ms DONE
  2026_05_03_213900_create_variant_option_values_table ........... 0.23ms DONE
  2026_05_03_213901_create_inventory_items_table ................. 0.30ms DONE
  2026_05_03_213902_create_collections_table ..................... 0.38ms DONE
  2026_05_03_213903_create_collection_products_table ............. 0.30ms DONE
  2026_05_03_213904_create_product_media_table ................... 0.43ms DONE
  2026_05_03_223824_create_themes_table .......................... 0.47ms DONE
  2026_05_03_223830_create_theme_files_table ..................... 0.35ms DONE
  2026_05_03_223837_create_theme_settings_table .................. 0.16ms DONE
  2026_05_03_223841_create_pages_table ........................... 0.37ms DONE
  2026_05_03_223844_create_navigation_menus_table ................ 0.30ms DONE
  2026_05_03_223848_create_navigation_items_table ................ 0.31ms DONE
  2026_05_03_225400_create_carts_table ........................... 0.39ms DONE
  2026_05_03_225405_create_cart_lines_table ...................... 0.67ms DONE
  2026_05_03_225409_create_checkouts_table ....................... 0.77ms DONE
  2026_05_03_225413_create_shipping_zones_table .................. 0.28ms DONE
  2026_05_03_225418_create_shipping_rates_table .................. 0.34ms DONE
  2026_05_03_225422_create_tax_settings_table .................... 0.25ms DONE
  2026_05_03_225427_create_discounts_table ....................... 0.55ms DONE

   INFO  Seeding database.

  Database\Seeders\OrganizationSeeder ................................ RUNNING
  Database\Seeders\OrganizationSeeder .............................. 5 ms DONE

  Database\Seeders\StoreSeeder ....................................... RUNNING
  Database\Seeders\StoreSeeder ..................................... 1 ms DONE

  Database\Seeders\StoreDomainSeeder ................................. RUNNING
  Database\Seeders\StoreDomainSeeder ............................... 1 ms DONE

  Database\Seeders\UserSeeder ........................................ RUNNING
  Database\Seeders\UserSeeder .................................... 269 ms DONE

  Database\Seeders\StoreUserSeeder ................................... RUNNING
  Database\Seeders\StoreUserSeeder ................................. 1 ms DONE

  Database\Seeders\StoreSettingsSeeder ............................... RUNNING
  Database\Seeders\StoreSettingsSeeder ............................. 1 ms DONE

  Database\Seeders\CollectionSeeder .................................. RUNNING
  Database\Seeders\CollectionSeeder ................................ 4 ms DONE

  Database\Seeders\ProductSeeder ..................................... RUNNING
  Database\Seeders\ProductSeeder ................................. 212 ms DONE

  Database\Seeders\ThemeSeeder ....................................... RUNNING
  Database\Seeders\ThemeSeeder .................................... 11 ms DONE

  Database\Seeders\ThemeFileSeeder ................................... RUNNING
  Database\Seeders\ThemeFileSeeder ................................. 2 ms DONE

  Database\Seeders\ThemeSettingsSeeder ............................... RUNNING
  Database\Seeders\ThemeSettingsSeeder ............................. 2 ms DONE

  Database\Seeders\PageSeeder ........................................ RUNNING
  Database\Seeders\PageSeeder ...................................... 3 ms DONE

  Database\Seeders\NavigationMenuSeeder .............................. RUNNING
  Database\Seeders\NavigationMenuSeeder ............................ 2 ms DONE

  Database\Seeders\NavigationItemSeeder .............................. RUNNING
  Database\Seeders\NavigationItemSeeder ............................ 6 ms DONE

  Database\Seeders\TaxSettingsSeeder ................................. RUNNING
  Database\Seeders\TaxSettingsSeeder ............................... 2 ms DONE

  Database\Seeders\ShippingZoneSeeder ................................ RUNNING
  Database\Seeders\ShippingZoneSeeder .............................. 1 ms DONE

  Database\Seeders\ShippingRateSeeder ................................ RUNNING
  Database\Seeders\ShippingRateSeeder .............................. 3 ms DONE

  Database\Seeders\DiscountSeeder .................................... RUNNING
  Database\Seeders\DiscountSeeder .................................. 4 ms DONE

  Database\Seeders\CustomerSeeder .................................... RUNNING
  Database\Seeders\CustomerSeeder ................................ 265 ms DONE


command: php artisan migrate:fresh --seed --no-interaction


message: All tests and the final seed pass are complete. I’m staging the UI slice now and checking the staged diff before committing.


text: All tests and the final seed pass are complete. I’m staging the UI slice now and checking the staged diff before committing.


command:
/bin/zsh
-lc
git diff --stat

cmd: git diff --stat

aggregated_output:
 app/Livewire/Storefront/Account/Auth/Login.php |  8 ++++++
 app/Livewire/Storefront/Products/Show.php      | 40 +++++++++++++++++++++++++-
 app/Services/CartService.php                   | 38 ++++++++++++++++++++++++
 public/favicon.svg                             |  6 ++--
 resources/views/layouts/storefront.blade.php   |  6 +++-
 resources/views/partials/head.blade.php        |  2 --
 routes/web.php                                 |  4 +++
 specs/progress.md                              | 28 ++++++++++++------
 8 files changed, 118 insertions(+), 14 deletions(-)


command:
/bin/zsh
-lc
git status --short

cmd: git status --short

aggregated_output:
 M app/Livewire/Storefront/Account/Auth/Login.php
 M app/Livewire/Storefront/Products/Show.php
 M app/Services/CartService.php
 M public/favicon.svg
 M resources/views/layouts/storefront.blade.php
 M resources/views/partials/head.blade.php
 M routes/web.php
 M specs/progress.md
?? .playwright-mcp/console-2026-05-03T23-28-42-443Z.log
?? .playwright-mcp/console-2026-05-03T23-32-09-299Z.log
?? app/Livewire/Storefront/Cart/
?? app/Livewire/Storefront/CartDrawer.php
?? app/Livewire/Storefront/Checkout/
?? resources/views/livewire/storefront/cart-drawer.blade.php
?? resources/views/livewire/storefront/cart/
?? resources/views/livewire/storefront/checkout/
?? tests/Feature/Storefront/CartCheckoutUiTest.php


output:
Chunk ID: f16983
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 143
Output:
 app/Livewire/Storefront/Account/Auth/Login.php |  8 ++++++
 app/Livewire/Storefront/Products/Show.php      | 40 +++++++++++++++++++++++++-
 app/Services/CartService.php                   | 38 ++++++++++++++++++++++++
 public/favicon.svg                             |  6 ++--
 resources/views/layouts/storefront.blade.php   |  6 +++-
 resources/views/partials/head.blade.php        |  2 --
 routes/web.php                                 |  4 +++
 specs/progress.md                              | 28 ++++++++++++------
 8 files changed, 118 insertions(+), 14 deletions(-)


output:
Chunk ID: d202cf
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 178
Output:
 M app/Livewire/Storefront/Account/Auth/Login.php
 M app/Livewire/Storefront/Products/Show.php
 M app/Services/CartService.php
 M public/favicon.svg
 M resources/views/layouts/storefront.blade.php
 M resources/views/partials/head.blade.php
 M routes/web.php
 M specs/progress.md
?? .playwright-mcp/console-2026-05-03T23-28-42-443Z.log
?? .playwright-mcp/console-2026-05-03T23-32-09-299Z.log
?? app/Livewire/Storefront/Cart/
?? app/Livewire/Storefront/CartDrawer.php
?? app/Livewire/Storefront/Checkout/
?? resources/views/livewire/storefront/cart-drawer.blade.php
?? resources/views/livewire/storefront/cart/
?? resources/views/livewire/storefront/checkout/
?? tests/Feature/Storefront/CartCheckoutUiTest.php


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


aggregated_output:
 M app/Livewire/Storefront/Account/Auth/Login.php
 M app/Livewire/Storefront/Products/Show.php
 M app/Services/CartService.php
 M public/favicon.svg
 M resources/views/layouts/storefront.blade.php
 M resources/views/partials/head.blade.php
 M routes/web.php
 M specs/progress.md
?? app/Livewire/Storefront/Cart/
?? app/Livewire/Storefront/CartDrawer.php
?? app/Livewire/Storefront/Checkout/
?? resources/views/livewire/storefront/cart-drawer.blade.php
?? resources/views/livewire/storefront/cart/
?? resources/views/livewire/storefront/checkout/
?? tests/Feature/Storefront/CartCheckoutUiTest.php


output:
Chunk ID: 68fd3f
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 150
Output:
 M app/Livewire/Storefront/Account/Auth/Login.php
 M app/Livewire/Storefront/Products/Show.php
 M app/Services/CartService.php
 M public/favicon.svg
 M resources/views/layouts/storefront.blade.php
 M resources/views/partials/head.blade.php
 M routes/web.php
 M specs/progress.md
?? app/Livewire/Storefront/Cart/
?? app/Livewire/Storefront/CartDrawer.php
?? app/Livewire/Storefront/Checkout/
?? resources/views/livewire/storefront/cart-drawer.blade.php
?? resources/views/livewire/storefront/cart/
?? resources/views/livewire/storefront/checkout/
?? tests/Feature/Storefront/CartCheckoutUiTest.php


cmd: git add app/Livewire/Storefront app/Services/CartService.php public/favicon.svg resources/views/layouts/storefront.blade.php resources/views/partials/head.blade.php resources/views/livewire/storefront routes/web.php specs/progress.md tests/Feature/Storefront/CartCheckoutUiTest.php


command: git add app/Livewire/Storefront app/Services/CartService.php public/favicon.svg resources/views/layouts/storefront.blade.php resources/views/partials/head.blade.php resources/views/livewire/storefront routes/web.php specs/progress.md tests/Feature/Storefront/CartCheckoutUiTest.php


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


command:
/bin/zsh
-lc
git diff --cached --check

cmd: git diff --cached --check


command:
/bin/zsh
-lc
git diff --cached --stat

cmd: git diff --cached --stat

aggregated_output:
 app/Livewire/Storefront/Account/Auth/Login.php     |   8 +
 app/Livewire/Storefront/Cart/Show.php              | 139 ++++++++
 app/Livewire/Storefront/CartDrawer.php             | 137 ++++++++
 app/Livewire/Storefront/Checkout/Show.php          | 353 +++++++++++++++++++++
 app/Livewire/Storefront/Products/Show.php          |  40 ++-
 app/Services/CartService.php                       |  38 +++
 public/favicon.svg                                 |   6 +-
 resources/views/layouts/storefront.blade.php       |   6 +-
 .../livewire/storefront/cart-drawer.blade.php      |  70 ++++
 .../views/livewire/storefront/cart/show.blade.php  |  84 +++++
 .../livewire/storefront/checkout/show.blade.php    | 218 +++++++++++++
 resources/views/partials/head.blade.php            |   2 -
 routes/web.php                                     |   4 +
 specs/progress.md                                  |  28 +-
 tests/Feature/Storefront/CartCheckoutUiTest.php    | 149 +++++++++
 15 files changed, 1268 insertions(+), 14 deletions(-)


aggregated_output:
M  app/Livewire/Storefront/Account/Auth/Login.php
A  app/Livewire/Storefront/Cart/Show.php
A  app/Livewire/Storefront/CartDrawer.php
A  app/Livewire/Storefront/Checkout/Show.php
M  app/Livewire/Storefront/Products/Show.php
M  app/Services/CartService.php
M  public/favicon.svg
M  resources/views/layouts/storefront.blade.php
A  resources/views/livewire/storefront/cart-drawer.blade.php
A  resources/views/livewire/storefront/cart/show.blade.php
A  resources/views/livewire/storefront/checkout/show.blade.php
M  resources/views/partials/head.blade.php
M  routes/web.php
M  specs/progress.md
A  tests/Feature/Storefront/CartCheckoutUiTest.php


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


output:
Chunk ID: da8aab
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 256
Output:
 app/Livewire/Storefront/Account/Auth/Login.php     |   8 +
 app/Livewire/Storefront/Cart/Show.php              | 139 ++++++++
 app/Livewire/Storefront/CartDrawer.php             | 137 ++++++++
 app/Livewire/Storefront/Checkout/Show.php          | 353 +++++++++++++++++++++
 app/Livewire/Storefront/Products/Show.php          |  40 ++-
 app/Services/CartService.php                       |  38 +++
 public/favicon.svg                                 |   6 +-
 resources/views/layouts/storefront.blade.php       |   6 +-
 .../livewire/storefront/cart-drawer.blade.php      |  70 ++++
 .../views/livewire/storefront/cart/show.blade.php  |  84 +++++
 .../livewire/storefront/checkout/show.blade.php    | 218 +++++++++++++
 resources/views/partials/head.blade.php            |   2 -
 routes/web.php                                     |   4 +
 specs/progress.md                                  |  28 +-
 tests/Feature/Storefront/CartCheckoutUiTest.php    | 149 +++++++++
 15 files changed, 1268 insertions(+), 14 deletions(-)


output:
Chunk ID: 624ec5
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 161
Output:
M  app/Livewire/Storefront/Account/Auth/Login.php
A  app/Livewire/Storefront/Cart/Show.php
A  app/Livewire/Storefront/CartDrawer.php
A  app/Livewire/Storefront/Checkout/Show.php
M  app/Livewire/Storefront/Products/Show.php
M  app/Services/CartService.php
M  public/favicon.svg
M  resources/views/layouts/storefront.blade.php
A  resources/views/livewire/storefront/cart-drawer.blade.php
A  resources/views/livewire/storefront/cart/show.blade.php
A  resources/views/livewire/storefront/checkout/show.blade.php
M  resources/views/partials/head.blade.php
M  routes/web.php
M  specs/progress.md
A  tests/Feature/Storefront/CartCheckoutUiTest.php


message: The staged diff check passed. I’m committing the storefront cart/checkout UI slice now.


text: The staged diff check passed. I’m committing the storefront cart/checkout UI slice now.


cmd: git commit -m "Connect storefront cart checkout UI"


command: git commit -m "Connect storefront cart checkout UI"

aggregated_output:
[2026-05-04-codex-5-5-goal bdf85923] Connect storefront cart checkout UI
 15 files changed, 1268 insertions(+), 14 deletions(-)
 create mode 100644 app/Livewire/Storefront/Cart/Show.php
 create mode 100644 app/Livewire/Storefront/CartDrawer.php
 create mode 100644 app/Livewire/Storefront/Checkout/Show.php
 create mode 100644 resources/views/livewire/storefront/cart-drawer.blade.php
 create mode 100644 resources/views/livewire/storefront/cart/show.blade.php
 create mode 100644 resources/views/livewire/storefront/checkout/show.blade.php
 create mode 100644 tests/Feature/Storefront/CartCheckoutUiTest.php


output:
Chunk ID: 1a7bb2
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 153
Output:
[2026-05-04-codex-5-5-goal bdf85923] Connect storefront cart checkout UI
 15 files changed, 1268 insertions(+), 14 deletions(-)
 create mode 100644 app/Livewire/Storefront/Cart/Show.php
 create mode 100644 app/Livewire/Storefront/CartDrawer.php
 create mode 100644 app/Livewire/Storefront/Checkout/Show.php
 create mode 100644 resources/views/livewire/storefront/cart-drawer.blade.php
 create mode 100644 resources/views/livewire/storefront/cart/show.blade.php
 create mode 100644 resources/views/livewire/storefront/checkout/show.blade.php
 create mode 100644 tests/Feature/Storefront/CartCheckoutUiTest.php


message: Committed `bdf85923`. I’m moving to the remaining Phase 4 API surface next: versioned storefront cart and checkout JSON endpoints with resources and focused API tests.


text: Committed `bdf85923`. I’m moving to the remaining Phase 4 API surface next: versioned storefront cart and checkout JSON endpoints with resources and focused API tests.


packages: laravel/framework pestphp/pest

queries: api routing laravel 12 apiResource json resources

queries: eloquent api resources conditional relationships


server: laravel-boost

tool: search-docs

type: text

text:
# Search Results

## Http Tests Testing JSON APIs
Http Tests | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/http-tests.md

## Testing JSON APIs

Laravel also provides several helpers for testing JSON APIs and their responses. For example, the `json`, `getJson`, `postJson`, `putJson`, `patchJson`, `deleteJson`, and `optionsJson` methods may be used to issue JSON requests with various HTTP verbs. You may also easily pass data and headers to these methods. To get started, let's write a test to make a `POST` request to `/api/user` and assert that the expected JSON data was returned:

```php tab=Pest
<?php

test('making an api request', function () {
    $response = $this->postJson('/api/user', ['name' => 'Sally']);

    $response
        ->assertStatus(201)
        ->assertJson([
            'created' => true,
        ]);
});
```

```php tab=PHPUnit
<?php

namespace Tests\Feature;

use Tests\TestCase;

class ExampleTest extends TestCase
{
    /**
     * A basic functional test example.
     */
    public function test_making_an_api_request(): void
    {
        $response = $this->postJson('/api/user', ['name' => 'Sally']);

        $response
            ->assertStatus(201)
            ->assertJson([
                'created' => true,
            ]);
    }
}
```

In addition, JSON response data may be accessed as array variables on the response, making it convenient for you to inspect the individual values returned within a JSON response:

```php tab=Pest
expect($response['created'])->toBeTrue();
```

```php tab=PHPUnit
$this->assertTrue($response['created']);
```

> [!NOTE]
> The `assertJson` method converts the response to an array to verify that the given array exists within the JSON response returned by the application. So, if there are other properties in the JSON response, this test will still pass as long as the given fragment is present.

---

## Validation Validation Quickstart Displaying the Validation Errors XHR Requests and Validation
Validation | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/validation.md

#### XHR Requests and Validation

In this example, we used a traditional form to send data to the application. However, many applications receive XHR requests from a JavaScript powered frontend. When using the `validate` method during an XHR request, Laravel will not generate a redirect response. Instead, Laravel generates a [JSON response containing all of the validation errors](#validation-error-response-format). This JSON response will be sent with a 422 HTTP status code.

---

## Pest3 Now Available New Configuration API
Pest3 Now Available | pestphp/pest@4.x | Source: https://github.com/pestphp/docs/blob/4.x/pest3-now-available.md

## New Configuration API

Pest 1 / Pest 2's configuration API was a little bit confusing, the `uses()` function that was originally made only for having the `$this` variable within closure bound to the test case instance, ended up being used for pretty much everything.

In Pest 3, we've introduced a new configuration API that is more intuitive and easier to use. The new configuration API is based on the `pest()` function, which allows you to configure Pest using a fluent and expressive API.

> Note: the `uses()` function is still available in Pest 3, and we don't have plans to remove it. However, we recommend using the new configuration API for new projects.

```diff
-uses(TestCase::class)->in(__DIR__);
+pest()->extends(TestCase::class);

-uses(TestCase::class, RefreshDatabase::class)->in('Features');
+pest()->extends(TestCase::class)->use(RefreshDatabase::class)->in('Features');

-uses()->compact();
+pest()->printer()->compact();
```

And of course, any method that was available on the `uses()` API, like `->beforeEach()` or `->group()` is still available on the new `pest()` configuration API; we've just made it more intuitive and easier to use.

---

## Urls URLs for Named Routes Eloquent Models
Urls | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/urls.md

#### Eloquent Models

You will often be generating URLs using the route key (typically the primary key) of [Eloquent models](/docs/{{version}}/eloquent). For this reason, you may pass Eloquent models as parameter values. The `route` helper will automatically extract the model's route key:

```php
echo route('post.show', ['post' => $post]);
```

---

## Validation Available Validation Rules json
Validation | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/validation.md

#### json

The field under validation must be a valid JSON string.

---

## Http Tests Testing JSON APIs Fluent JSON Testing
Http Tests | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/http-tests.md

### Fluent JSON Testing

Laravel also offers a beautiful way to fluently test your application's JSON responses. To get started, pass a closure to the `assertJson` method. This closure will be invoked with an instance of `Illuminate\Testing\Fluent\AssertableJson` which can be used to make assertions against the JSON that was returned by your application. The `where` method may be used to make assertions against a particular attribute of the JSON, while the `missing` method may be used to assert that a particular attribute is missing from the JSON:

```php tab=Pest
use Illuminate\Testing\Fluent\AssertableJson;

test('fluent json', function () {
    $response = $this->getJson('/users/1');

    $response
        ->assertJson(fn (AssertableJson $json) =>
            $json->where('id', 1)
                ->where('name', 'Victoria Faith')
                ->where('email', fn (string $email) => str($email)->is('victoria@gmail.com'))
                ->whereNot('status', 'pending')
                ->missing('password')
                ->etc()
        );
});
```

```php tab=PHPUnit
use Illuminate\Testing\Fluent\AssertableJson;

/**
 * A basic functional test example.
 */
public function test_fluent_json(): void
{
    $response = $this->getJson('/users/1');

    $response
        ->assertJson(fn (AssertableJson $json) =>
            $json->where('id', 1)
                ->where('name', 'Victoria Faith')
                ->where('email', fn (string $email) => str($email)->is('victoria@gmail.com'))
                ->whereNot('status', 'pending')
                ->missing('password')
                ->etc()
        );
}
```

#### Understanding the `etc` Method

In the example above, you may have noticed we invoked the `etc` method at the end of our assertion chain. This method informs Laravel that there may be other attributes present on the JSON object. If the `etc` method is not used, the test will fail if other attributes that you did not make assertions against exist on the JSON object.

The intention behind this behavior is to protect you from unintentionally exposing sensitive information in your JSON responses by forcing you to either explicitly make an assertion against the attribute or explicitly allow additional attributes via the `etc` method.

However, you should be aware that not including the `etc` method in your assertion chain does not ensure that additional attributes are not being added to arrays that are nested within your JSON object. The `etc` method only ensures that no additional attributes exist at the nesting level in which the `etc` method is invoked.

---

## Cli Api Reference
Cli Api Reference | pestphp/pest@4.x | Source: https://github.com/pestphp/docs/blob/4.x/cli-api-reference.md

---
title: CLI API Reference
description: In the preceding chapters of the Pest documentation, we have covered numerous CLI options that are available in Pest. Nevertheless, Pest provides many other options that could prove beneficial. The complete CLI API Reference is provided below for your convenience.
---

# CLI API Reference

In the preceding chapters of the Pest documentation, we have covered numerous CLI options that are available in Pest. Nevertheless, Pest provides many other options that could prove beneficial. The complete CLI API Reference is provided below for your convenience.

## Configuration

- `--init`: Initialize a standard Pest configuration.
- `--bootstrap <file>` A PHP script that is included before the tests run.
- `-c|--configuration <file>`: Read configuration from XML file.
- `--no-configuration`: Ignore default configuration file (phpunit.xml).
- `--extension <class>`: Register test runner extension with bootstrap <class>.
- `--no-extensions`: Do not load PHPUnit extensions.
- `--include-path <path(s)>`: Prepend PHP's include_path with given path(s).
- `-d <key[=value]>`: Set a php.ini value.
- `--cache-directory <dir>`: Specify cache directory.
- `--generate-configuration`: Generate configuration file with suggested settings.
- `--migrate-configuration`: Migrate configuration file to current format.
- `--generate-baseline <file>`: Generate baseline for issues.
- `--use-baseline <file>`: Use baseline to ignore issues.
- `--ignore-baseline`: Do not use baseline to ignore issues.
- `--test-directory`: Specify test directory containing Pest.php, TestCase.php, helpers and your tests. Default: tests

## Selection

- `--bail`: Stop execution upon first not-passed test.
- `--ci`: Ignore focused tests using `->only()` and run the entire test suite.
- `--todos`: Output to standard output the list of todos.
- `--notes`: Output to standard output tests with notes.
- `--issue`: Output to standard output tests with the given issue number.
- `--pr`: Output to standard output tests with the given pull request number.
- `--pull-request`: Output to standard output tests with the given pull request number (alias for `--pr`).
- `--flaky`: Output to standard output tests marked as flaky.
- `--retry`: Run non-passing tests first and stop execution upon first error or failure.
- `--list-suites` List available test suites.
- `--testsuite <name>`: Only run tests from the specified test suite(s).
- `--exclude-testsuite <name>`: Exclude tests from the specified test suite(s).
- `--list-groups`: List available test groups.
- `--group <name>`: Only run tests from the specified group(s).
- `--exclude-group <name>`: Exclude tests from the specified group(s).
- `--covers <name>`: Only run tests that intend to cover `<name>`.
- `--uses <name>`: Only run tests that intend to use `<name>`.
- `--requires-php-extension <name>`: Only run tests that require PHP extension <name>.
- `--list-test-files`: List available test files.
- `--list-tests`: List available tests.
- `--list-tests-xml <file>`: List available tests in XML format.
- `--filter <pattern>`: Filter which tests to run
- `--exclude-filter <pattern>`: Exclude tests for the specified filter pattern.
- `--test-suffix <suffixes>`: Only search for test in files with specified suffix(es). Default: Test.php,.phpt

## Execution

- `--parallel` Run tests in parallel.
- `--shard <index/total>`: Run only the given shard of tests (e.g. `--shard=1/5`). Uses time-balanced distribution when `tests/.pest/shards.json` exists.
- `--update-shards`: Update `tests/.pest/shards.json` with test timing data for time-balanced sharding. Can be combined with `--parallel`.
- `--update-snapshots`: Update snapshots for tests using the "toMatchSnapshot" expectation.
- `--globals-backup`: Backup and restore $GLOBALS for each test.
- `--static-backup`: Backup and restore static properties for each test.
- `--strict-coverage`: Be strict about code coverage metadata.
- `--strict-global-state`: Be strict about changes to global state.
- `--disallow-test-output`: Be strict about output during tests.
- `--enforce-time-limit`: Enforce time limit based on test size.
- `--default-time-limit <sec>`: Timeout in seconds for tests that have no declared size.
- `--dont-report-useless-tests`: Do not report tests that do not test anything.
- `--stop-on-defect`: Stop execution upon first not-passed test.
- `--stop-on-error`: Stop execution upon first error.
- `--stop-on-failure`: Stop execution upon first error or failure.
- `--stop-on-warning`: Stop execution upon first warning.
- `--stop-on-risky`: Stop execution upon first risky test.
- `--stop-on-deprecation`: Stop after first test that triggered a deprecation.
- `--stop-on-notice`: Stop after first test that triggered a notice.
- `--stop-on-skipped`: Stop execution upon first skipped test.
- `--stop-on-incomplete`: Stop execution upon first incomplete test.
- `--fail-on-empty-test-suite`: Signal failure using shell exit code when no tests were run.
- `--fail-on-warning`: Treat tests with warnings as failures.
- `--fail-on-risky`: Treat risky tests as failures.
- `--fail-on-deprecation`: Signal failure using shell exit code when a deprecation was triggered.
- `--fail-on-phpunit-deprecation`: Signal failure using shell exit code when a PHPUnit deprecation was triggered.
- `--fail-on-notice`: Signal failure using shell exit code when a notice was triggered.
- `--fail-on-skipped`: Treat skipped tests as failures.
- `--fail-on-incomplete`: Signal failure using shell exit code when a test was marked incomplete.
- `--cache-result`: Write test results to cache file.
- `--do-not-cache-result`: Do not write test results to cache file
- `--order-by <order>`: Run tests in order: default|defects|depends|duration|no-depends|random|reverse|size.
- `--random-order-seed <N>`: Use the specified random seed when running tests in random order

## Reporting

- `--colors <flag>`: Use colors in output ("never", "auto" or "always").
- `--columns <n>`: Number of columns to use for progress output.
- `--columns max`: Use maximum number of columns for progress output.
- `--stderr`: Write to STDERR instead of STDOUT.
- `--no-progress`: Disable output of test execution progress.
- `--no-results`: Disable output of test results.
- `--no-output`: Disable all output.
- `--display-incomplete`: Display details for incomplete tests.
- `--display-skipped`: Display details for skipped tests.
- `--display-deprecations`: Display details for deprecations triggered by tests.
- `--display-phpunit-deprecations`: Display details for PHPUnit deprecations.
- `--display-errors`: Display details for errors triggered by tests.
- `--display-notices`: Display details for notices triggered by tests.
- `--display-warnings`: Display details for warnings triggered by tests.
- `--reverse-list`: Print defects in reverse order.
- `--teamcity`: Replace default progress and result output with TeamCity format.
- `--testdox`: Replace default result output with TestDox format
- `--testdox-summary`: Repeat TestDox output for tests with errors, failures, or issues.
- `--debug`: Replace default progress and result output with debugging information.
- `--compact`:  Replace default result output with Compact format

## Logging

- `--log-junit <file>`: Write test results in JUnit XML format to file.
- `--log-teamcity <file>`: Write test results in TeamCity format to file.
- `--testdox-html <file>`: Write test results in TestDox format (HTML) to file.
- `--testdox-text <file>`: Write test results in TestDox format (plain text) to file.
- `--log-events-text <file>`: Stream events as plain text to file.
- `--log-events-verbose-text <file>`: Stream events as plain text (with telemetry information) to file.
- `--no-logging`: Ignore logging configured in the XML configuration file

## Code Coverage

- `--coverage`: Generate code coverage report and output to standard output.
- `--coverage --min=<value>`: Set the minimum required coverage percentage, and fail if not met.
- `--coverage --exactly=<value>`: Set the exact required coverage percentage, and fail if not met.
- `--coverage --only-covered`: Hide files with 0% coverage from the code coverage report.
- `--coverage-clover <file>`: Write code coverage report in Clover XML format to file.
- `--coverage-cobertura <file>`: Write code coverage report in Cobertura XML format to file.
- `--coverage-crap4j <file>`: Write code coverage report in Crap4J XML format to file.
- `--coverage-html <dir>`: Write code coverage report in HTML format to directory.
- `--coverage-php <file>`: Write serialized code coverage data to file.
- `--coverage-text=<file>`: Write code coverage report in text format to file [default: standard output].
- `--only-summary-for-coverage-text`: Option for code coverage report in text format: only show summary.
- `--show-uncovered-for-coverage-text`: Option for code coverage report in text format: show uncovered files.
- `--coverage-xml <dir>`: Write code coverage report in XML format to directory.
- `--warm-coverage-cache`: Warm static analysis cache.
- `--coverage-filter <dir>`: Include `<dir>`: in code coverage reporting.
- `--path-coverage`: Report path coverage in addition to line coverage.
- `--disable-coverage-ignore`: Disable metadata for ignoring code coverage.
- `--no-coverage`: Ignore code coverage reporting configured in the XML configuration file

## Mutation Testing

- `--mutate`: Runs mutation testing, to understand the quality of your tests.
- `--mutate --parallel`: Runs mutation testing in parallel.
- `--mutate --min`: Set the minimum required mutation score, and fail if not met.
- `--mutate --id`:  Run only the mutation with the given ID. But E.g. --id=ecb35ab30ffd3491. Note, you need to provide the same options as the original run.
- `--mutate --covered-only`: Only generate mutations for classes that are covered by tests.
- `--mutate --bail`: Stop mutation testing execution upon first untested or uncovered mutation.
- `--mutate --class`: Generate mutations for the given class(es). E.g. --class=App\\Models.
- `--mutate --ignore`: Ignore the given class(es) when generating mutations. E.g. --ignore=App\\Http\\Requests.
- `--mutate --clear-cache`: Clear the mutation cache.
- `--mutate --no-cache`: Clear the mutation cache.
- `--mutate --ignore-min-score-on-zero-mutations`: Ignore the minimum score requirement when there are no mutations.
- `--mutate --covered-only`: Only generate mutations for classes that are covered by tests.
- `--mutate --everything`: Generate mutations for all classes, even if they are not covered by tests.
- `--mutate --profile`: Output to standard output the top ten slowest mutations.
- `--mutate --retry`: Run untested or uncovered mutations first and stop execution upon first error or failure.
- `--mutate --stop-on-uncovered`: Stop mutation testing execution upon first untested mutation.
- `--mutate --stop-on-untested`: Stop mutation testing execution upon first untested mutation.

## Profiling

- `--profile`: Output to standard output the top ten slowest tests

---

In this chapter, you found a complete list of CLI options provided by Pest. In the subsequent documentation, we will explore the topic of test dependencies: [Test Dependencies](/docs/test-dependencies)

---

## Validation Form Request Validation
Validation | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/validation.md

## Form Request Validation

---

## Upgrade Upgrading To 12.0 From 11.x Eloquent Models and UUIDv7
Upgrade | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/upgrade.md

#### Models and UUIDv7

**Likelihood Of Impact: Medium**

The `HasUuids` trait now returns UUIDs that are compatible with version 7 of the UUID spec (ordered UUIDs). If you would like to continue using ordered UUIDv4 strings for your model's IDs, you should now use the `HasVersion4Uuids` trait:

```php
use Illuminate\Database\Eloquent\Concerns\HasUuids; // [tl! remove]
use Illuminate\Database\Eloquent\Concerns\HasVersion4Uuids as HasUuids; // [tl! add]
```

The `HasVersion7Uuids` trait has been removed. If you were previously using this trait, you should use the `HasUuids` trait instead, which now provides the same behavior.

---

## Controllers Resource Controllers Partial Resource Routes API Resource Routes
Controllers | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/controllers.md

#### API Resource Routes

When declaring resource routes that will be consumed by APIs, you will commonly want to exclude routes that present HTML templates such as `create` and `edit`. For convenience, you may use the `apiResource` method to automatically exclude these two routes:

```php
use App\Http\Controllers\PhotoController;

Route::apiResource('photos', PhotoController::class);
```

You may register many API resource controllers at once by passing an array to the `apiResources` method:

```php
use App\Http\Controllers\PhotoController;
use App\Http\Controllers\PostController;

Route::apiResources([
    'photos' => PhotoController::class,
    'posts' => PostController::class,
]);
```

To quickly generate an API resource controller that does not include the `create` or `edit` methods, use the `--api` switch when executing the `make:controller` command:

```shell
php artisan make:controller PhotoController --api
```

---

## Eloquent Resources Writing Resources Conditional Relationships
Eloquent Resources | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/eloquent-resources.md

### Conditional Relationships

In addition to conditionally loading attributes, you may conditionally include relationships on your resource responses based on if the relationship has already been loaded on the model. This allows your controller to decide which relationships should be loaded on the model and your resource can easily include them only when they have actually been loaded. Ultimately, this makes it easier to avoid "N+1" query problems within your resources.

The `whenLoaded` method may be used to conditionally load a relationship. In order to avoid unnecessarily loading relationships, this method accepts the name of the relationship instead of the relationship itself:

```php
use App\Http\Resources\PostResource;

/**
 * Transform the resource into an array.
 *
 * @return array<string, mixed>
 */
public function toArray(Request $request): array
{
    return [
        'id' => $this->id,
        'name' => $this->name,
        'email' => $this->email,
        'posts' => PostResource::collection($this->whenLoaded('posts')),
        'created_at' => $this->created_at,
        'updated_at' => $this->updated_at,
    ];
}
```

In this example, if the relationship has not been loaded, the `posts` key will be removed from the resource response before it is sent to the client.

---

## Validation Available Validation Rules date_format:_format_,...
Validation | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/validation.md

#### date_format:_format_,...

The field under validation must match one of the given _formats_. You should use **either** `date` or `date_format` when validating a field, not both. This validation rule supports all formats supported by PHP's [DateTime](https://www.php.net/manual/en/class.datetime.php) class.

For convenience, date-based rules may be constructed using the fluent `date` rule builder:

```php
use Illuminate\Validation\Rule;

'start_date' => [
    'required',
    Rule::date()->format('Y-m-d'),
],
```

---

## Sanctum Mobile Application Authentication Issuing API Tokens
Sanctum | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/sanctum.md

### Issuing API Tokens

To get started, create a route that accepts the user's email / username, password, and device name, then exchanges those credentials for a new Sanctum token. The "device name" given to this endpoint is for informational purposes and may be any value you wish. In general, the device name value should be a name the user would recognize, such as "Nuno's iPhone 17".

Typically, you will make a request to the token endpoint from your mobile application's "login" screen. The endpoint will return the plain-text API token which may then be stored on the mobile device and used to make additional API requests:

```php
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\ValidationException;

Route::post('/sanctum/token', function (Request $request) {
    $request->validate([
        'email' => 'required|email',
        'password' => 'required',
        'device_name' => 'required',
    ]);

    $user = User::where('email', $request->email)->first();

    if (! $user || ! Hash::check($request->password, $user->password)) {
        throw ValidationException::withMessages([
            'email' => ['The provided credentials are incorrect.'],
        ]);
    }

    return $user->createToken($request->device_name)->plainTextToken;
});
```

When the mobile application uses the token to make an API request to your application, it should pass the token in the `Authorization` header as a `Bearer` token.

> [!NOTE]
> When issuing tokens for a mobile application, you are also free to specify [token abilities](#token-abilities).

---

## Scout Searching Where Clauses Customizing the Eloquent Results Query
Scout | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/scout.md

#### Customizing the Eloquent Results Query

After Scout retrieves a list of matching Eloquent models from your application's search engine, Eloquent is used to retrieve all of the matching models by their primary keys. You may customize this query by invoking the `query` method. The `query` method accepts a closure that will receive the Eloquent query builder instance as an argument:

```php
use App\Models\Order;
use Illuminate\Database\Eloquent\Builder;

$orders = Order::search('Star Trek')
    ->query(fn (Builder $query) => $query->with('invoices'))
    ->get();
```

When using a third-party engine, this callback is invoked after the relevant models have already been retrieved from the search engine, so it should not be used for "filtering" results — use [Scout where clauses](#where-clauses) instead. However, when using the database engine, the `query` method's constraints are applied directly to the database query, so you may use it for filtering as well.

---

## Validation Form Request Validation Preparing Input for Validation
Validation | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/validation.md

### Preparing Input for Validation

If you need to prepare or sanitize any data from the request before you apply your validation rules, you may use the `prepareForValidation` method:

```php
use Illuminate\Support\Str;

/**
 * Prepare the data for validation.
 */
protected function prepareForValidation(): void
{
    $this->merge([
        'slug' => Str::slug($this->slug),
    ]);
}
```

Likewise, if you need to normalize any request data after validation is complete, you may use the `passedValidation` method:

```php
/**
 * Handle a passed validation attempt.
 */
protected function passedValidation(): void
{
    $this->replace(['name' => 'Taylor']);
}
```

---

## Pennant Adding Custom Pennant Drivers Defining Features Externally
Pennant | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/pennant.md

### Defining Features Externally

If your driver is a wrapper around a third-party feature flag platform, you will likely define features on the platform rather than using Pennant's `Feature::define` method. If that is the case, your custom driver should also implement the `Laravel\Pennant\Contracts\DefinesFeaturesExternally` interface:

```php
<?php

namespace App\Extensions;

use Laravel\Pennant\Contracts\Driver;
use Laravel\Pennant\Contracts\DefinesFeaturesExternally;

class FeatureFlagServiceDriver implements Driver, DefinesFeaturesExternally
{
    /**
     * Get the features defined for the given scope.
     */
    public function definedFeaturesForScope(mixed $scope): array {}

    /* ... */
}
```

The `definedFeaturesForScope` method should return a list of feature names defined for the provided scope.

---

## Validation Manually Creating Validators Performing Additional Validation
Validation | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/validation.md

### Performing Additional Validation

Sometimes you need to perform additional validation after your initial validation is complete. You can accomplish this using the validator's `after` method. The `after` method accepts a closure or an array of callables which will be invoked after validation is complete. The given callables will receive an `Illuminate\Validation\Validator` instance, allowing you to raise additional error messages if necessary:

```php
use Illuminate\Support\Facades\Validator;

$validator = Validator::make(/* ... */);

$validator->after(function ($validator) {
    if ($this->somethingElseIsInvalid()) {
        $validator->errors()->add(
            'field', 'Something is wrong with this field!'
        );
    }
});

if ($validator->fails()) {
    // ...
}
```

As noted, the `after` method also accepts an array of callables, which is particularly convenient if your "after validation" logic is encapsulated in invokable classes, which will receive an `Illuminate\Validation\Validator` instance via their `__invoke` method:

```php
use App\Validation\ValidateShippingTime;
use App\Validation\ValidateUserStatus;

$validator->after([
    new ValidateUserStatus,
    new ValidateShippingTime,
    function ($validator) {
        // ...
    },
]);
```

---

## Sanctum API Token Authentication Token Expiration
Sanctum | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/sanctum.md

### Token Expiration

By default, Sanctum tokens never expire and may only be invalidated by [revoking the token](#revoking-tokens). However, if you would like to configure an expiration time for your application's API tokens, you may do so via the `expiration` configuration option defined in your application's `sanctum` configuration file. This configuration option defines the number of minutes until an issued token will be considered expired:

```php
'expiration' => 525600,
```

If you would like to specify the expiration time of each token independently, you may do so by providing the expiration time as the third argument to the `createToken` method:

```php
return $user->createToken(
    'token-name', ['*'], now()->plus(weeks: 1)
)->plainTextToken;
```

If you have configured a token expiration time for your application, you may also wish to [schedule a task](/docs/{{version}}/scheduling) to prune your application's expired tokens. Thankfully, Sanctum includes a `sanctum:prune-expired` Artisan command that you may use to accomplish this. For example, you may configure a scheduled task to delete all expired token database records that have been expired for at least 24 hours:

```php
use Illuminate\Support\Facades\Schedule;

Schedule::command('sanctum:prune-expired --hours=24')->daily();
```

---

## Controllers Resource Controllers Singleton Resource Controllers API Singleton Resources
Controllers | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/controllers.md

#### API Singleton Resources

The `apiSingleton` method may be used to register a singleton resource that will be manipulated via an API, thus rendering the `create` and `edit` routes unnecessary:

```php
Route::apiSingleton('profile', ProfileController::class);
```

Of course, API singleton resources may also be `creatable`, which will register `store` and `destroy` routes for the resource:

```php
Route::apiSingleton('photos.thumbnail', ProfileController::class)->creatable();
```

_[Truncated]_