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

namespace App\Livewire\Storefront\Products;

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

class Show extends Component
{
    public string $handle;

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

    public int $quantity = 1;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        $inventory = $variant->inventoryItem;

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

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

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

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

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

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

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

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

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


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

show.blade.php

    <x-storefront.breadcrumbs :items="[
        ['label' => $product->collections->first()?->title ?? 'Products', 'url' => $product->collections->first() ? route('collections.show', $product->collections->first()->handle) : route('collections.index')],
        ['label' => $product->title],
    ]" />

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

                    @if ($selectedVariant?->compare_at_amount && $selectedVariant->compare_at_amount > $selectedVariant->price_amount)
                        Sale
                    @endif
                    @foreach ($product->tags ?? [] as $tag)
                        {{ $tag }}
                    @endforeach

{{ $product->title }}

                @if ($selectedVariant)
                    <x-storefront.price :amount="$selectedVariant->price_amount" :compare-at="$selectedVariant->compare_at_amount" :currency="$selectedVariant->currency" class="text-2xl" aria-live="polite" />
                @endif

            @if ($product->options->isNotEmpty())

                    @foreach ($product->options as $option)
                        <fieldset class="space-y-3" wire:key="product-option-selector-{{ $option->getKey() }}">
                            <legend class="text-sm font-medium text-zinc-950 dark:text-white">{{ $option->name }}</legend>

                                @foreach ($option->values as $value)
                                    @php($selected = ($selectedOptions[$option->name] ?? null) === $value->value)
                                    <button
                                        type="button"
                                        wire:click="selectOption(@js($option->name), @js($value->value))"
                                        @class([
                                            'min-h-10 rounded-md border px-4 py-2 text-sm font-medium transition',
                                            'border-blue-700 bg-blue-50 text-blue-800 dark:border-blue-300 dark:bg-blue-950 dark:text-blue-100' => $selected,
                                            'border-zinc-200 text-zinc-700 hover:border-zinc-400 dark:border-zinc-700 dark:text-zinc-200 dark:hover:border-zinc-500' => ! $selected,
                                        ])
                                        aria-pressed="{{ $selected ? 'true' : 'false' }}"
                                    >
                                        {{ $value->value }}
                                    </button>
                                @endforeach

                        </fieldset>
                    @endforeach

            @endif

{{ $stockState['message'] }}

                    <button type="button" wire:click="decreaseQuantity" class="flex size-11 items-center justify-center border-r border-zinc-200 text-lg dark:border-zinc-700" aria-label="Decrease quantity">-</button>
                    <input wire:model.live="quantity" type="number" min="1" class="h-11 w-full border-0 bg-transparent text-center text-sm" aria-label="Quantity">
                    <button type="button" wire:click="increaseQuantity" class="flex size-11 items-center justify-center border-l border-zinc-200 text-lg dark:border-zinc-700" aria-label="Increase quantity">+</button>

                <flux:button wire:click="addToCart" variant="primary" class="w-full" :disabled="! $this->canAddToCart()">
                    {{ $this->canAddToCart() ? 'Add to cart' : 'Sold out' }}
                </flux:button>

            @if ($product->description_html)

                    {!! $product->description_html !!}

            @endif

Vendor

{{ $product->vendor ?: '-' }}

Product type

{{ $product->product_type ?: '-' }}

                @if ($selectedVariant)

SKU

{{ $selectedVariant->sku ?: '-' }}

Shipping

{{ $selectedVariant->requires_shipping ? 'Required' : 'Digital delivery' }}

                @endif


Output:
Chunk ID: 4cb5ef
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 1586
Output:

    <x-storefront.breadcrumbs :items="[
        ['label' => $product->collections->first()?->title ?? 'Products', 'url' => $product->collections->first() ? route('collections.show', $product->collections->first()->handle) : route('collections.index')],
        ['label' => $product->title],
    ]" />

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

                    @if ($selectedVariant?->compare_at_amount && $selectedVariant->compare_at_amount > $selectedVariant->price_amount)
                        Sale
                    @endif
                    @foreach ($product->tags ?? [] as $tag)
                        {{ $tag }}
                    @endforeach

{{ $product->title }}

                @if ($selectedVariant)
                    <x-storefront.price :amount="$selectedVariant->price_amount" :compare-at="$selectedVariant->compare_at_amount" :currency="$selectedVariant->currency" class="text-2xl" aria-live="polite" />
                @endif

            @if ($product->options->isNotEmpty())

                    @foreach ($product->options as $option)
                        <fieldset class="space-y-3" wire:key="product-option-selector-{{ $option->getKey() }}">
                            <legend class="text-sm font-medium text-zinc-950 dark:text-white">{{ $option->name }}</legend>

                                @foreach ($option->values as $value)
                                    @php($selected = ($selectedOptions[$option->name] ?? null) === $value->value)
                                    <button
                                        type="button"
                                        wire:click="selectOption(@js($option->name), @js($value->value))"
                                        @class([
                                            'min-h-10 rounded-md border px-4 py-2 text-sm font-medium transition',
                                            'border-blue-700 bg-blue-50 text-blue-800 dark:border-blue-300 dark:bg-blue-950 dark:text-blue-100' => $selected,
                                            'border-zinc-200 text-zinc-700 hover:border-zinc-400 dark:border-zinc-700 dark:text-zinc-200 dark:hover:border-zinc-500' => ! $selected,
                                        ])
                                        aria-pressed="{{ $selected ? 'true' : 'false' }}"
                                    >
                                        {{ $value->value }}
                                    </button>
                                @endforeach

                        </fieldset>
                    @endforeach

            @endif

{{ $stockState['message'] }}

                    <button type="button" wire:click="decreaseQuantity" class="flex size-11 items-center justify-center border-r border-zinc-200 text-lg dark:border-zinc-700" aria-label="Decrease quantity">-</button>
                    <input wire:model.live="quantity" type="number" min="1" class="h-11 w-full border-0 bg-transparent text-center text-sm" aria-label="Quantity">
                    <button type="button" wire:click="increaseQuantity" class="flex size-11 items-center justify-center border-l border-zinc-200 text-lg dark:border-zinc-700" aria-label="Increase quantity">+</button>

                <flux:button wire:click="addToCart" variant="primary" class="w-full" :disabled="! $this->canAddToCart()">
                    {{ $this->canAddToCart() ? 'Add to cart' : 'Sold out' }}
                </flux:button>

            @if ($product->description_html)

                    {!! $product->description_html !!}

            @endif

Vendor

{{ $product->vendor ?: '-' }}

Product type

{{ $product->product_type ?: '-' }}

                @if ($selectedVariant)

SKU

{{ $selectedVariant->sku ?: '-' }}

Shipping

{{ $selectedVariant->requires_shipping ? 'Required' : 'Digital delivery' }}

                @endif


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

Home.php

<?php

namespace App\Livewire\Storefront;

use App\Models\Collection;
use App\Models\Product;
use App\Models\Store;
use App\Services\ThemeSettingsService;
use Illuminate\Support\Collection as SupportCollection;
use Livewire\Component;

class Home extends Component
{
    public function store(): Store
    {
        $store = app('current_store');

        abort_unless($store instanceof Store, 404);

        return $store;
    }

    public function featuredProducts(): SupportCollection
    {
        return Product::query()
            ->with(['variants.inventoryItem'])
            ->withCount('variants')
            ->where('status', 'active')
            ->whereNotNull('published_at')
            ->oldest('id')
            ->limit((int) data_get($this->themeSettings(), 'home.featured_product_limit', 8))
            ->get();
    }

    public function featuredCollections(): SupportCollection
    {
        return Collection::query()
            ->withCount('products')
            ->where('status', 'active')
            ->orderBy('title')
            ->limit((int) data_get($this->themeSettings(), 'home.featured_collection_limit', 4))
            ->get();
    }

    /**
     * @return array<string, mixed>
     */
    public function themeSettings(): array
    {
        return app(ThemeSettingsService::class)->forStore($this->store());
    }

    public function render(): mixed
    {
        return view('livewire.storefront.home', [
            'store' => $this->store(),
            'featuredProducts' => $this->featuredProducts(),
            'featuredCollections' => $this->featuredCollections(),
            'themeSettings' => $this->themeSettings(),
        ])->layout('layouts.storefront', [
            'title' => $this->store()->name,
        ]);
    }
}


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

home.blade.php

    @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: ce3ef5
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 1030
Output:

    @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: d54398
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 446
Output:
<?php

namespace App\Livewire\Storefront;

use App\Models\Collection;
use App\Models\Product;
use App\Models\Store;
use App\Services\ThemeSettingsService;
use Illuminate\Support\Collection as SupportCollection;
use Livewire\Component;

class Home extends Component
{
    public function store(): Store
    {
        $store = app('current_store');

        abort_unless($store instanceof Store, 404);

        return $store;
    }

    public function featuredProducts(): SupportCollection
    {
        return Product::query()
            ->with(['variants.inventoryItem'])
            ->withCount('variants')
            ->where('status', 'active')
            ->whereNotNull('published_at')
            ->oldest('id')
            ->limit((int) data_get($this->themeSettings(), 'home.featured_product_limit', 8))
            ->get();
    }

    public function featuredCollections(): SupportCollection
    {
        return Collection::query()
            ->withCount('products')
            ->where('status', 'active')
            ->orderBy('title')
            ->limit((int) data_get($this->themeSettings(), 'home.featured_collection_limit', 4))
            ->get();
    }

    /**
     * @return array<string, mixed>
     */
    public function themeSettings(): array
    {
        return app(ThemeSettingsService::class)->forStore($this->store());
    }

    public function render(): mixed
    {
        return view('livewire.storefront.home', [
            'store' => $this->store(),
            'featuredProducts' => $this->featuredProducts(),
            'featuredCollections' => $this->featuredCollections(),
            'themeSettings' => $this->themeSettings(),
        ])->layout('layouts.storefront', [
            'title' => $this->store()->name,
        ]);
    }
}


rg -n "current_store|storefront|Livewire update|middleware" app bootstrap routes config resources tests


sed -n '1,220p' app/Http/Middleware/ResolveStore.php


/bin/zsh
-lc
sed -n '1,220p' bootstrap/app.php

<?php

use App\Http\Middleware\CheckStoreRole;
use App\Http\Middleware\ResolveStore;
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;

return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(
        web: __DIR__.'/../routes/web.php',
        commands: __DIR__.'/../routes/console.php',
        health: '/up',
    )
    ->withMiddleware(function (Middleware $middleware): void {
        $middleware->alias([
            'role.check' => CheckStoreRole::class,
            'store.resolve' => ResolveStore::class,
        ]);

        $middleware->appendToGroup('storefront', [
            ResolveStore::class,
        ]);

        $middleware->appendToGroup('admin', [
            ResolveStore::class,
            CheckStoreRole::class,
        ]);
    })
    ->withExceptions(function (Exceptions $exceptions): void {
        //
    })->create();


sed -n '1,220p' app/Http/Middleware/ResolveStore.php

<?php

namespace App\Http\Middleware;

use App\Enums\StoreStatus;
use App\Models\Store;
use App\Models\StoreDomain;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Symfony\Component\HttpFoundation\Response;

class ResolveStore
{
    /**
     * Handle an incoming request.
     *
     * @param  \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response)  $next
     */
    public function handle(Request $request, Closure $next): Response
    {
        $store = $request->is('admin*')
            ? $this->resolveAdminStore($request)
            : $this->resolveStorefrontStore($request);

        abort_unless($store, 404);

        app()->instance('current_store', $store);

        if (! $request->is('admin*') && $store->status === StoreStatus::Suspended) {
            abort(503);
        }

        if ($request->is('admin*') && $store->status === StoreStatus::Suspended && ! $request->isMethodSafe()) {
            abort(403);
        }

        return $next($request);
    }

    private function resolveStorefrontStore(Request $request): ?Store
    {
        $hostname = $request->getHost();
        $cacheKey = "store_domain:{$hostname}";

        $storeId = Cache::remember($cacheKey, now()->addMinutes(5), function () use ($hostname): ?int {
            return StoreDomain::query()
                ->where('hostname', $hostname)
                ->value('store_id');
        });

        return $storeId ? Store::query()->find($storeId) : null;
    }

    private function resolveAdminStore(Request $request): ?Store
    {
        $user = $request->user();

        if (! $user) {
            return null;
        }

        $storeId = $request->session()->get('current_store_id');

        if (! $storeId) {
            $storeId = $user->stores()->oldest('stores.id')->value('stores.id');

            if ($storeId) {
                $request->session()->put('current_store_id', $storeId);
            }
        }

        if (! $storeId || ! $user->stores()->whereKey($storeId)->exists()) {
            return null;
        }

        return Store::query()->find($storeId);
    }
}


rg -n "current_store|storefront|Livewire update|middleware" app bootstrap routes config resources tests

rg -n 'current_store|storefront|Livewire update|middleware' app bootstrap routes config resources tests

Question: current_store|storefront|Livewire update|middleware

routes/web.php:20:Route::middleware(['storefront'])->group(function (): void {
routes/web.php:30:    ->middleware('guest')
routes/web.php:40:})->middleware('auth')->name('admin.logout');
routes/web.php:42:Route::middleware(['auth', 'verified', 'admin'])->prefix('admin')->name('admin.')->group(function (): void {
routes/web.php:53:Route::middleware(['storefront'])->group(function (): void {
routes/web.php:55:        ->middleware('guest:customer')
routes/web.php:59:        ->middleware('guest:customer')
routes/web.php:62:    Route::view('account', 'storefront.account.dashboard')
routes/web.php:63:        ->middleware('auth:customer')
routes/web.php:68:    ->middleware(['auth', 'verified'])
tests/Feature/Cart/CartServiceTest.php:22:    app()->instance('current_store', $store);
tests/Feature/Checkout/PricingServicesTest.php:23:    app()->instance('current_store', $store);
tests/Feature/Foundation/TenancyTest.php:26:test('storefront requests resolve a store by hostname', function () {
tests/Feature/Foundation/TenancyTest.php:33:test('storefront requests reject unknown or suspended stores', function () {
tests/Feature/Foundation/TenancyTest.php:56:        ->withSession(['current_store_id' => $store->getKey()])
tests/Feature/Checkout/CheckoutServiceTest.php:24:    app()->instance('current_store', $store);
tests/Feature/Foundation/AuthorizationTest.php:34:    app()->instance('current_store', $store);
tests/Feature/Foundation/AuthorizationTest.php:50:    app()->instance('current_store', $store);
routes/settings.php:10:Route::middleware(['auth'])->group(function () {
routes/settings.php:16:Route::middleware(['auth', 'verified'])->group(function () {
routes/settings.php:21:        ->middleware(
tests/Feature/Foundation/CustomerAuthTest.php:29:    app()->instance('current_store', $firstStore);
tests/Feature/Foundation/CustomerAuthTest.php:43:    app()->instance('current_store', $secondStore);
tests/Feature/Foundation/CustomerAuthTest.php:59:    app()->instance('current_store', $store);
tests/Feature/Foundation/CustomerAuthTest.php:74:    app()->instance('current_store', $store);
bootstrap/app.php:15:    ->withMiddleware(function (Middleware $middleware): void {
bootstrap/app.php:16:        $middleware->alias([
bootstrap/app.php:21:        $middleware->appendToGroup('storefront', [
bootstrap/app.php:25:        $middleware->appendToGroup('admin', [
resources/views/layouts/storefront.blade.php:7:        @php($store = app()->bound('current_store') ? app('current_store') : null)
tests/Feature/Catalog/ProductServiceTest.php:34:    app()->instance('current_store', $store);
tests/Feature/Catalog/ProductServiceTest.php:50:    app()->instance('current_store', $store);
tests/Feature/Catalog/ProductServiceTest.php:70:    app()->instance('current_store', $store);
tests/Feature/Catalog/ProductServiceTest.php:119:    app()->instance('current_store', $store);
tests/Feature/Catalog/ProductServiceTest.php:144:    app()->instance('current_store', $store);
tests/Feature/Catalog/CatalogFoundationTest.php:44:    app()->instance('current_store', $firstStore);
tests/Feature/Catalog/CatalogFoundationTest.php:64:    app()->instance('current_store', $firstStore);
tests/Feature/Catalog/CatalogFoundationTest.php:77:    app()->instance('current_store', $resolvedStore);
tests/Feature/Catalog/InventoryServiceTest.php:14:    app()->instance('current_store', $store);
tests/Feature/Catalog/InventoryServiceTest.php:39:    app()->instance('current_store', $store);
tests/Feature/Catalog/VariantMatrixServiceTest.php:15:    app()->instance('current_store', $store);
tests/Feature/Catalog/VariantMatrixServiceTest.php:43:    app()->instance('current_store', $store);
tests/Feature/Catalog/VariantMatrixServiceTest.php:56:    app()->instance('current_store', $store);
tests/Feature/Catalog/CatalogUiTest.php:22:test('storefront catalog browsing routes render seeded products', function () {
tests/Feature/Catalog/CatalogUiTest.php:78:    app()->instance('current_store', $store);
tests/Feature/Catalog/CatalogUiTest.php:158:    app()->instance('current_store', $store);
config/fortify.php:98:    | Here you may specify which middleware Fortify will assign to the routes
config/fortify.php:100:    | these middleware but typically this provided default is preferred.
config/fortify.php:104:    'middleware' => ['web'],
tests/Feature/Storefront/ThemeDataTest.php:52:    app()->instance('current_store', $firstStore);
tests/Feature/Storefront/ThemeDataTest.php:74:    app()->instance('current_store', $firstStore);
tests/Feature/Storefront/ThemeDataTest.php:108:test('storefront pages render published database content only', function () {
tests/Feature/Storefront/ThemeDataTest.php:127:        ->assertSee('Acme Fashion is the demo storefront');
app/Livewire/Admin/Collections/Form.php:36:            $store = app('current_store');
app/Livewire/Admin/Collections/Form.php:71:        $store = app('current_store');
app/Livewire/Admin/Products/Form.php:58:            $store = app('current_st…2990 chars truncated…:query()->findOrFail($this->storeId));
app/Livewire/Storefront/Account/Auth/Login.php:72:        return view('livewire.storefront.account.auth.login')
app/Services/ThemeSettingsService.php:63:                'tagline' => 'A self-contained demo storefront with scoped catalog data and checkout-ready products.',
app/Livewire/Storefront/Search/Index.php:51:        return view('livewire.storefront.search.index', [
app/Livewire/Storefront/Search/Index.php:53:        ])->layout('layouts.storefront', [
app/Models/NavigationItem.php:40:        static::addGlobalScope('current_store', function (Builder $builder): void {
app/Models/NavigationItem.php:41:            if (! app()->bound('current_store')) {
app/Models/NavigationItem.php:45:            $store = app('current_store');
app/Livewire/Storefront/Home.php:16:        $store = app('current_store');
app/Livewire/Storefront/Home.php:55:        return view('livewire.storefront.home', [
app/Livewire/Storefront/Home.php:60:        ])->layout('layouts.storefront', [
app/Http/Middleware/ResolveStore.php:28:        app()->instance('current_store', $store);
app/Http/Middleware/ResolveStore.php:63:        $storeId = $request->session()->get('current_store_id');
app/Http/Middleware/ResolveStore.php:69:                $request->session()->put('current_store_id', $storeId);
app/Models/ThemeSettings.php:31:        static::addGlobalScope('current_store', function (Builder $builder): void {
app/Models/ThemeSettings.php:32:            if (! app()->bound('current_store')) {
app/Models/ThemeSettings.php:36:            $store = app('current_store');
app/Models/ProductOptionValue.php:36:        static::addGlobalScope('current_store', function (Builder $builder): void {
app/Models/ProductOptionValue.php:37:            if (! app()->bound('current_store')) {
app/Models/ProductOptionValue.php:41:            $store = app('current_store');
app/Livewire/Storefront/Products/Show.php:140:        return view('livewire.storefront.products.show', [
app/Livewire/Storefront/Products/Show.php:144:        ])->layout('layouts.storefront', [
app/Http/Middleware/CheckStoreRole.php:20:        $store = app()->bound('current_store') ? app('current_store') : null;
app/Models/ProductVariant.php:51:        static::addGlobalScope('current_store', function (Builder $builder): void {
app/Models/ProductVariant.php:52:            if (! app()->bound('current_store')) {
app/Models/ProductVariant.php:56:            $store = app('current_store');
app/Models/ProductMedia.php:47:        static::addGlobalScope('current_store', function (Builder $builder): void {
app/Models/ProductMedia.php:48:            if (! app()->bound('current_store')) {
app/Models/ProductMedia.php:52:            $store = app('current_store');
app/Enums/StoreDomainType.php:7:    case Storefront = 'storefront';
resources/views/livewire/storefront/home.blade.php:22:                        <x-storefront.product-card :product="$product" />
resources/views/livewire/storefront/home.blade.php:61:                <x-storefront.product-card :product="$product" wire:key="home-product-{{ $product->getKey() }}" />
resources/views/livewire/storefront/pages/show.blade.php:2:    <x-storefront.breadcrumbs :items="[['label' => $title]]" />
app/Providers/AppServiceProvider.php:67:        RateLimiter::for('api.storefront', function (Request $request): Limit {
app/Providers/AppServiceProvider.php:90:        View::composer('layouts.storefront', function (ViewInstance $view): void {
app/Providers/AppServiceProvider.php:91:            $store = app()->bound('current_store') ? app('current_store') : null;
resources/views/livewire/storefront/collections/show.blade.php:2:    <x-storefront.breadcrumbs :items="[
resources/views/livewire/storefront/collections/show.blade.php:76:                        <x-storefront.product-card :product="$product" wire:key="collection-product-{{ $product->getKey() }}" />
resources/views/livewire/storefront/collections/index.blade.php:2:    <x-storefront.breadcrumbs :items="[['label' => 'Collections']]" />
app/Auth/CustomerUserProvider.php:36:        $store = app()->bound('current_store') ? app('current_store') : null;
resources/views/livewire/storefront/products/show.blade.php:2:    <x-storefront.breadcrumbs :items="[
resources/views/livewire/storefront/products/show.blade.php:28:                    <x-storefront.price :amount="$selectedVariant->price_amount" :compare-at="$selectedVariant->compare_at_amount" :currency="$selectedVariant->currency" class="text-2xl" aria-live="polite" />
resources/views/livewire/storefront/search/index.blade.php:2:    <x-storefront.breadcrumbs :items="[['label' => 'Search results']]" />
resources/views/livewire/storefront/search/index.blade.php:29:                    <x-storefront.product-card :product="$product" wire:key="search-product-{{ $product->getKey() }}" />
app/Traits/ChecksStoreRole.php:54:        if (! app()->bound('current_store')) {
app/Traits/ChecksStoreRole.php:58:        $store = app('current_store');


Output:
Chunk ID: 7f05b2
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 3248
Output:
routes/web.php:20:Route::middleware(['storefront'])->group(function (): void {
routes/web.php:30:    ->middleware('guest')
routes/web.php:40:})->middleware('auth')->name('admin.logout');
routes/web.php:42:Route::middleware(['auth', 'verified', 'admin'])->prefix('admin')->name('admin.')->group(function (): void {
routes/web.php:53:Route::middleware(['storefront'])->group(function (): void {
routes/web.php:55:        ->middleware('guest:customer')
routes/web.php:59:        ->middleware('guest:customer')
routes/web.php:62:    Route::view('account', 'storefront.account.dashboard')
routes/web.php:63:        ->middleware('auth:customer')
routes/web.php:68:    ->middleware(['auth', 'verified'])
tests/Feature/Cart/CartServiceTest.php:22:    app()->instance('current_store', $store);
tests/Feature/Checkout/PricingServicesTest.php:23:    app()->instance('current_store', $store);
tests/Feature/Foundation/TenancyTest.php:26:test('storefront requests resolve a store by hostname', function () {
tests/Feature/Foundation/TenancyTest.php:33:test('storefront requests reject unknown or suspended stores', function () {
tests/Feature/Foundation/TenancyTest.php:56:        ->withSession(['current_store_id' => $store->getKey()])
tests/Feature/Checkout/CheckoutServiceTest.php:24:    app()->instance('current_store', $store);
tests/Feature/Foundation/AuthorizationTest.php:34:    app()->instance('current_store', $store);
tests/Feature/Foundation/AuthorizationTest.php:50:    app()->instance('current_store', $store);
routes/settings.php:10:Route::middleware(['auth'])->group(function () {
routes/settings.php:16:Route::middleware(['auth', 'verified'])->group(function () {
routes/settings.php:21:        ->middleware(
tests/Feature/Foundation/CustomerAuthTest.php:29:    app()->instance('current_store', $firstStore);
tests/Feature/Foundation/CustomerAuthTest.php:43:    app()->instance('current_store', $secondStore);
tests/Feature/Foundation/CustomerAuthTest.php:59:    app()->instance('current_store', $store);
tests/Feature/Foundation/CustomerAuthTest.php:74:    app()->instance('current_store', $store);
bootstrap/app.php:15:    ->withMiddleware(function (Middleware $middleware): void {
bootstrap/app.php:16:        $middleware->alias([
bootstrap/app.php:21:        $middleware->appendToGroup('storefront', [
bootstrap/app.php:25:        $middleware->appendToGroup('admin', [
resources/views/layouts/storefront.blade.php:7:        @php($store = app()->bound('current_store') ? app('current_store') : null)
tests/Feature/Catalog/ProductServiceTest.php:34:    app()->instance('current_store', $store);
tests/Feature/Catalog/ProductServiceTest.php:50:    app()->instance('current_store', $store);
tests/Feature/Catalog/ProductServiceTest.php:70:    app()->instance('current_store', $store);
tests/Feature/Catalog/ProductServiceTest.php:119:    app()->instance('current_store', $store);
tests/Feature/Catalog/ProductServiceTest.php:144:    app()->instance('current_store', $store);
tests/Feature/Catalog/CatalogFoundationTest.php:44:    app()->instance('current_store', $firstStore);
tests/Feature/Catalog/CatalogFoundationTest.php:64:    app()->instance('current_store', $firstStore);
tests/Feature/Catalog/CatalogFoundationTest.php:77:    app()->instance('current_store', $resolvedStore);
tests/Feature/Catalog/InventoryServiceTest.php:14:    app()->instance('current_store', $store);
tests/Feature/Catalog/InventoryServiceTest.php:39:    app()->instance('current_store', $store);
tests/Feature/Catalog/VariantMatrixServiceTest.php:15:    app()->instance('current_store', $store);
tests/Feature/Catalog/VariantMatrixServiceTest.php:43:    app()->instance('current_store', $store);
tests/Feature/Catalog/VariantMatrixServiceTest.php:56:    app()->instance('current_store', $store);
tests/Feature/Catalog/CatalogUiTest.php:22:test('storefront catalog browsing routes render seeded products', function () {
tests/Feature/Catalog/CatalogUiTest.php:78:    app()->instance('current_store', $store);
tests/Feature/Catalog/CatalogUiTest.php:158:    app()->instance('current_store', $store);
config/fortify.php:98:    | Here you may specify which middleware Fortify will assign to the routes
config/fortify.php:100:    | these middleware but typically this provided default is preferred.
config/fortify.php:104:    'middleware' => ['web'],
tests/Feature/Storefront/ThemeDataTest.php:52:    app()->instance('current_store', $firstStore);
tests/Feature/Storefront/ThemeDataTest.php:74:    app()->instance('current_store', $firstStore);
tests/Feature/Storefront/ThemeDataTest.php:108:test('storefront pages render published database content only', function () {
tests/Feature/Storefront/ThemeDataTest.php:127:        ->assertSee('Acme Fashion is the demo storefront');
app/Livewire/Admin/Collections/Form.php:36:            $store = app('current_store');
app/Livewire/Admin/Collections/Form.php:71:        $store = app('current_store');
app/Livewire/Admin/Products/Form.php:58:            $store = app('current_store');
app/Livewire/Admin/Products/Form.php:102:        $store = app('current_store');
app/Models/Concerns/BelongsToStore.php:15:            if ($model->store_id || ! app()->bound('current_store')) {
app/Models/Concerns/BelongsToStore.php:19:            $store = app('current_store');
resources/views/components/storefront/product-card.blade.php:32:                <x-storefront.price :amount="$variant->price_amount" :compare-at="$variant->compare_at_amount" :currency="$variant->currency" class="text-sm" />
app/Livewire/Storefront/Pages/Show.php:32:        return view('livewire.storefront.pages.show')
app/Livewire/Storefront/Pages/Show.php:33:            ->layout('layouts.storefront', [
app/Models/Scopes/StoreScope.php:14:        if (! app()->bound('current_store')) {
app/Models/Scopes/StoreScope.php:18:        $store = app('current_store');
app/Models/ProductOption.php:36:        static::addGlobalScope('current_store', function (Builder $builder): void {
app/Models/ProductOption.php:37:            if (! app()->bound('current_store')) {
app/Models/ProductOption.php:41:            $store = app('current_store');
app/Livewire/Storefront/Collections/Show.php:121:        return view('livewire.storefront.collections.show', [
app/Livewire/Storefront/Collections/Show.php:126:        ])->layout('layouts.storefront', [
app/Models/ThemeFile.php:37:        static::addGlobalScope('current_store', function (Builder $builder): void {
app/Models/ThemeFile.php:38:            if (! app()->bound('current_store')) {
app/Models/ThemeFile.php:42:            $store = app('current_store');
resources/views/livewire/admin/collections/index.blade.php:5:            <flux:text class="mt-1">Group products for storefront browsing and merchandising.</flux:text>
app/Models/ShippingRate.php:40:        static::addGlobalScope('current_store', function (Builder $builder): void {
app/Models/ShippingRate.php:41:            if (! app()->bound('current_store')) {
app/Models/ShippingRate.php:45:            $store = app('current_store');
app/Livewire/Storefront/Collections/Index.php:24:        return view('livewire.storefront.collections.index', [
app/Livewire/Storefront/Collections/Index.php:26:        ])->layout('layouts.storefront', [
app/Models/CartLine.php:43:        static::addGlobalScope('current_store', function (Builder $builder): void {
app/Models/CartLine.php:44:            if (! app()->bound('current_store')) {
app/Models/CartLine.php:48:            $store = app('current_store');
app/Livewire/Storefront/Account/Auth/Register.php:27:        $store = app('current_store');
app/Livewire/Storefront/Account/Auth/Register.php:38:        app()->instance('current_store', $store);
app/Livewire/Storefront/Account/Auth/Register.php:71:        return view('livewire.storefront.account.auth.register')
app/Livewire/Storefront/Account/Auth/Login.php:23:        $store = app('current_store');
app/Livewire/Storefront/Account/Auth/Login.php:48:        app()->instance('current_store', Store::query()->findOrFail($this->storeId));
app/Livewire/Storefront/Account/Auth/Login.php:72:        return view('livewire.storefront.account.auth.login')
app/Services/ThemeSettingsService.php:63:                'tagline' => 'A self-contained demo storefront with scoped catalog data and checkout-ready products.',
app/Livewire/Storefront/Search/Index.php:51:        return view('livewire.storefront.search.index', [
app/Livewire/Storefront/Search/Index.php:53:        ])->layout('layouts.storefront', [
app/Models/NavigationItem.php:40:        static::addGlobalScope('current_store', function (Builder $builder): void {
app/Models/NavigationItem.php:41:            if (! app()->bound('current_store')) {
app/Models/NavigationItem.php:45:            $store = app('current_store');
app/Livewire/Storefront/Home.php:16:        $store = app('current_store');
app/Livewire/Storefront/Home.php:55:        return view('livewire.storefront.home', [
app/Livewire/Storefront/Home.php:60:        ])->layout('layouts.storefront', [
app/Http/Middleware/ResolveStore.php:28:        app()->instance('current_store', $store);
app/Http/Middleware/ResolveStore.php:63:        $storeId = $request->session()->get('current_store_id');
app/Http/Middleware/ResolveStore.php:69:                $request->session()->put('current_store_id', $storeId);
app/Models/ThemeSettings.php:31:        static::addGlobalScope('current_store', function (Builder $builder): void {
app/Models/ThemeSettings.php:32:            if (! app()->bound('current_store')) {
app/Models/ThemeSettings.php:36:            $store = app('current_store');
app/Models/ProductOptionValue.php:36:        static::addGlobalScope('current_store', function (Builder $builder): void {
app/Models/ProductOptionValue.php:37:            if (! app()->bound('current_store')) {
app/Models/ProductOptionValue.php:41:            $store = app('current_store');
app/Livewire/Storefront/Products/Show.php:140:        return view('livewire.storefront.products.show', [
app/Livewire/Storefront/Products/Show.php:144:        ])->layout('layouts.storefront', [
app/Http/Middleware/CheckStoreRole.php:20:        $store = app()->bound('current_store') ? app('current_store') : null;
app/Models/ProductVariant.php:51:        static::addGlobalScope('current_store', function (Builder $builder): void {
app/Models/ProductVariant.php:52:            if (! app()->bound('current_store')) {
app/Models/ProductVariant.php:56:            $store = app('current_store');
app/Models/ProductMedia.php:47:        static::addGlobalScope('current_store', function (Builder $builder): void {
app/Models/ProductMedia.php:48:            if (! app()->bound('current_store')) {
app/Models/ProductMedia.php:52:            $store = app('current_store');
app/Enums/StoreDomainType.php:7:    case Storefront = 'storefront';
resources/views/livewire/storefront/home.blade.php:22:                        <x-storefront.product-card :product="$product" />
resources/views/livewire/storefront/home.blade.php:61:                <x-storefront.product-card :product="$product" wire:key="home-product-{{ $product->getKey() }}" />
resources/views/livewire/storefront/pages/show.blade.php:2:    <x-storefront.breadcrumbs :items="[['label' => $title]]" />
app/Providers/AppServiceProvider.php:67:        RateLimiter::for('api.storefront', function (Request $request): Limit {
app/Providers/AppServiceProvider.php:90:        View::composer('layouts.storefront', function (ViewInstance $view): void {
app/Providers/AppServiceProvider.php:91:            $store = app()->bound('current_store') ? app('current_store') : null;
resources/views/livewire/storefront/collections/show.blade.php:2:    <x-storefront.breadcrumbs :items="[
resources/views/livewire/storefront/collections/show.blade.php:76:                        <x-storefront.product-card :product="$product" wire:key="collection-product-{{ $product->getKey() }}" />
resources/views/livewire/storefront/collections/index.blade.php:2:    <x-storefront.breadcrumbs :items="[['label' => 'Collections']]" />
app/Auth/CustomerUserProvider.php:36:        $store = app()->bound('current_store') ? app('current_store') : null;
resources/views/livewire/storefront/products/show.blade.php:2:    <x-storefront.breadcrumbs :items="[
resources/views/livewire/storefront/products/show.blade.php:28:                    <x-storefront.price :amount="$selectedVariant->price_amount" :compare-at="$selectedVariant->compare_at_amount" :currency="$selectedVariant->currency" class="text-2xl" aria-live="polite" />
resources/views/livewire/storefront/search/index.blade.php:2:    <x-storefront.breadcrumbs :items="[['label' => 'Search results']]" />
resources/views/livewire/storefront/search/index.blade.php:29:                    <x-storefront.product-card :product="$product" wire:key="search-product-{{ $product->getKey() }}" />
app/Traits/ChecksStoreRole.php:54:        if (! app()->bound('current_store')) {
app/Traits/ChecksStoreRole.php:58:        $store = app('current_store');


Output:
Chunk ID: 6544a2
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 538
Output:
<?php

namespace App\Http\Middleware;

use App\Enums\StoreStatus;
use App\Models\Store;
use App\Models\StoreDomain;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Cache;
use Symfony\Component\HttpFoundation\Response;

class ResolveStore
{
    /**
     * Handle an incoming request.
     *
     * @param  \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response)  $next
     */
    public function handle(Request $request, Closure $next): Response
    {
        $store = $request->is('admin*')
            ? $this->resolveAdminStore($request)
            : $this->resolveStorefrontStore($request);

        abort_unless($store, 404);

        app()->instance('current_store', $store);

        if (! $request->is('admin*') && $store->status === StoreStatus::Suspended) {
            abort(503);
        }

        if ($request->is('admin*') && $store->status === StoreStatus::Suspended && ! $request->isMethodSafe()) {
            abort(403);
        }

        return $next($request);
    }

    private function resolveStorefrontStore(Request $request): ?Store
    {
        $hostname = $request->getHost();
        $cacheKey = "store_domain:{$hostname}";

        $storeId = Cache::remember($cacheKey, now()->addMinutes(5), function () use ($hostname): ?int {
            return StoreDomain::query()
                ->where('hostname', $hostname)
                ->value('store_id');
        });

        return $storeId ? Store::query()->find($storeId) : null;
    }

    private function resolveAdminStore(Request $request): ?Store
    {
        $user = $request->user();

        if (! $user) {
            return null;
        }

        $storeId = $request->session()->get('current_store_id');

        if (! $storeId) {
            $storeId = $user->stores()->oldest('stores.id')->value('stores.id');

            if ($storeId) {
                $request->session()->put('current_store_id', $storeId);
            }
        }

        if (! $storeId || ! $user->stores()->whereKey($storeId)->exists()) {
            return null;
        }

        return Store::query()->find($storeId);
    }
}


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

use App\Http\Middleware\CheckStoreRole;
use App\Http\Middleware\ResolveStore;
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;

return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(
        web: __DIR__.'/../routes/web.php',
        commands: __DIR__.'/../routes/console.php',
        health: '/up',
    )
    ->withMiddleware(function (Middleware $middleware): void {
        $middleware->alias([
            'role.check' => CheckStoreRole::class,
            'store.resolve' => ResolveStore::class,
        ]);

        $middleware->appendToGroup('storefront', [
            ResolveStore::class,
        ]);

        $middleware->appendToGroup('admin', [
            ResolveStore::class,
            CheckStoreRole::class,
        ]);
    })
    ->withExceptions(function (Exceptions $exceptions): void {
        //
    })->create();


sed -n '1,140p' app/Livewire/Storefront/Account/Auth/Login.php


sed -n '1,140p' app/Livewire/Storefront/Account/Auth/Register.php


sed -n '1,140p' tests/Feature/Catalog/CatalogUiTest.php


sed -n '1,120p' tests/Feature/Storefront/ThemeDataTest.php


sed -n '1,140p' app/Livewire/Storefront/Account/Auth/Register.php

<?php

namespace App\Livewire\Storefront\Account\Auth;

use App\Models\Customer;
use App\Models\Store;
use Illuminate\Support\Facades\Auth;
use Illuminate\Validation\Rule;
use Livewire\Component;

class Register extends Component
{
    public string $name = '';

    public string $email = '';

    public string $password = '';

    public string $password_confirmation = '';

    public bool $marketing_opt_in = false;

    public int $storeId;

    public function mount(): void
    {
        $store = app('current_store');

        abort_unless($store instanceof Store, 404);

        $this->storeId = $store->getKey();
    }

    public function register(): void
    {
        $store = Store::query()->findOrFail($this->storeId);

        app()->instance('current_store', $store);

        $validated = $this->validate([
            'name' => ['required', 'string', 'max:255'],
            'email' => [
                'required',
                'email',
                'max:255',
                Rule::unique('customers', 'email')->where('store_id', $store->getKey()),
            ],
            'password' => ['required', 'string', 'min:8', 'confirmed'],
            'marketing_opt_in' => ['boolean'],
        ]);

        $customer = Customer::withoutGlobalScopes()->create([
            'store_id' => $store->getKey(),
            'name' => $validated['name'],
            'email' => $validated['email'],
            'password' => $validated['password'],
            'marketing_opt_in' => $validated['marketing_opt_in'],
        ]);

        Auth::guard('customer')->login($customer);

        if (request()->hasSession()) {
            request()->session()->regenerate();
        }

        $this->redirectRoute('account.dashboard', navigate: true);
    }

    public function render(): mixed
    {
        return view('livewire.storefront.account.auth.register')
            ->layout('layouts.auth');
    }
}


sed -n '1,140p' app/Livewire/Storefront/Account/Auth/Login.php

<?php

namespace App\Livewire\Storefront\Account\Auth;

use App\Models\Store;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Validation\ValidationException;
use Livewire\Component;

class Login extends Component
{
    public int $storeId;

    public string $email = '';

    public string $password = '';

    public bool $remember = false;

    public function mount(): void
    {
        $store = app('current_store');

        abort_unless($store instanceof Store, 404);

        $this->storeId = $store->getKey();
    }

    public function login(): void
    {
        $this->validate([
            'email' => ['required', 'email'],
            'password' => ['required', 'string'],
            'remember' => ['boolean'],
        ]);

        $key = 'customer-login:'.request()->ip();

        if (RateLimiter::tooManyAttempts($key, 5)) {
            throw ValidationException::withMessages([
                'email' => __('Too many attempts. Try again in :seconds seconds.', [
                    'seconds' => RateLimiter::availableIn($key),
                ]),
            ]);
        }

        app()->instance('current_store', Store::query()->findOrFail($this->storeId));

        if (! Auth::guard('customer')->attempt([
            'email' => $this->email,
            'password' => $this->password,
        ], $this->remember)) {
            RateLimiter::hit($key, 60);

            throw ValidationException::withMessages([
                'email' => __('Invalid credentials'),
            ]);
        }

        RateLimiter::clear($key);

        if (request()->hasSession()) {
            request()->session()->regenerate();
        }

        $this->redirectRoute('account.dashboard', navigate: true);
    }

    public function render(): mixed
    {
        return view('livewire.storefront.account.auth.login')
            ->layout('layouts.auth');
    }
}


Output:
Chunk ID: 84964e
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 481
Output:
<?php

namespace App\Livewire\Storefront\Account\Auth;

use App\Models\Store;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\RateLimiter;
use Illuminate\Validation\ValidationException;
use Livewire\Component;

class Login extends Component
{
    public int $storeId;

    public string $email = '';

    public string $password = '';

    public bool $remember = false;

    public function mount(): void
    {
        $store = app('current_store');

        abort_unless($store instanceof Store, 404);

        $this->storeId = $store->getKey();
    }

    public function login(): void
    {
        $this->validate([
            'email' => ['required', 'email'],
            'password' => ['required', 'string'],
            'remember' => ['boolean'],
        ]);

        $key = 'customer-login:'.request()->ip();

        if (RateLimiter::tooManyAttempts($key, 5)) {
            throw ValidationException::withMessages([
                'email' => __('Too many attempts. Try again in :seconds seconds.', [
                    'seconds' => RateLimiter::availableIn($key),
                ]),
            ]);
        }

        app()->instance('current_store', Store::query()->findOrFail($this->storeId));

        if (! Auth::guard('customer')->attempt([
            'email' => $this->email,
            'password' => $this->password,
        ], $this->remember)) {
            RateLimiter::hit($key, 60);

            throw ValidationException::withMessages([
                'email' => __('Invalid credentials'),
            ]);
        }

        RateLimiter::clear($key);

        if (request()->hasSession()) {
            request()->session()->regenerate();
        }

        $this->redirectRoute('account.dashboard', navigate: true);
    }

    public function render(): mixed
    {
        return view('livewire.storefront.account.auth.login')
            ->layout('layouts.auth');
    }
}


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

namespace App\Livewire\Storefront\Account\Auth;

use App\Models\Customer;
use App\Models\Store;
use Illuminate\Support\Facades\Auth;
use Illuminate\Validation\Rule;
use Livewire\Component;

class Register extends Component
{
    public string $name = '';

    public string $email = '';

    public string $password = '';

    public string $password_confirmation = '';

    public bool $marketing_opt_in = false;

    public int $storeId;

    public function mount(): void
    {
        $store = app('current_store');

        abort_unless($store instanceof Store, 404);

        $this->storeId = $store->getKey();
    }

    public function register(): void
    {
        $store = Store::query()->findOrFail($this->storeId);

        app()->instance('current_store', $store);

        $validated = $this->validate([
            'name' => ['required', 'string', 'max:255'],
            'email' => [
                'required',
                'email',
                'max:255',
                Rule::unique('customers', 'email')->where('store_id', $store->getKey()),
            ],
            'password' => ['required', 'string', 'min:8', 'confirmed'],
            'marketing_opt_in' => ['boolean'],
        ]);

        $customer = Customer::withoutGlobalScopes()->create([
            'store_id' => $store->getKey(),
            'name' => $validated['name'],
            'email' => $validated['email'],
            'password' => $validated['password'],
            'marketing_opt_in' => $validated['marketing_opt_in'],
        ]);

        Auth::guard('customer')->login($customer);

        if (request()->hasSession()) {
            request()->session()->regenerate();
        }

        $this->redirectRoute('account.dashboard', navigate: true);
    }

    public function render(): mixed
    {
        return view('livewire.storefront.account.auth.register')
            ->layout('layouts.auth');
    }
}


sed -n '1,140p' tests/Feature/Catalog/CatalogUiTest.php

<?php

use App\Livewire\Admin\Collections\Form as CollectionForm;
use App\Livewire\Admin\Products\Form as ProductForm;
use App\Livewire\Admin\Products\Index as ProductIndex;
use App\Models\Collection;
use App\Models\Product;
use App\Models\Store;
use App\Models\User;
use App\Services\ProductService;
use Database\Seeders\DatabaseSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Livewire\Livewire;

uses(RefreshDatabase::class);

beforeEach(function () {
    $this->withoutVite();
    $this->seed(DatabaseSeeder::class);
});

test('storefront catalog browsing routes render seeded products', function () {
    $this->withHeader('Host', 'shop.test')
        ->get('/')
        ->assertSuccessful()
        ->assertSee('Acme Fashion')
        ->assertSee('Classic Cotton T-Shirt')
        ->assertSee('24.99 EUR');

    $this->withHeader('Host', 'shop.test')
        ->get('/collections/t-shirts')
        ->assertSuccessful()
        ->assertSee('T-Shirts')
        ->assertSee('Classic Cotton T-Shirt')
        ->assertDontSee('Unreleased Winter Jacket');

    $this->withHeader('Host', 'shop.test')
        ->get('/products/classic-cotton-t-shirt')
        ->assertSuccessful()
        ->assertSee('Classic Cotton T-Shirt')
        ->assertSee('Size')
        ->assertSee('Color')
        ->assertSee('Add to cart');

    $this->withHeader('Host', 'shop.test')
        ->get('/search?q=draft')
        ->assertSuccessful()
        ->assertDontSee('Unreleased Winter Jacket');
});

test('admin catalog routes require authentication and render for store admins', function () {
    $this->get('/admin/products')->assertRedirect('/admin/login');

    $user = User::query()->where('email', 'admin@acme.test')->firstOrFail();

    $this->actingAs($user)
        ->get('/admin/products')
        ->assertSuccessful()
        ->assertSee('Classic Cotton T-Shirt')
        ->assertSee('Premium Slim Fit Jeans');

    $this->actingAs($user)
        ->get('/admin/collections')
        ->assertSuccessful()
        ->assertSee('Collections')
        ->assertSee('T-Shirts');

    $this->actingAs($user)
        ->get('/admin/inventory')
        ->assertSuccessful()
        ->assertSee('Inventory')
        ->assertSee('Limited Edition Sneakers');
});

test('admin product form creates edits archives and index filters products', function () {
    $store = Store::query()->where('handle', 'acme-fashion')->firstOrFail();
    $user = User::query()->where('email', 'admin@acme.test')->firstOrFail();
    app()->instance('current_store', $store);

    Livewire::actingAs($user)
        ->test(ProductForm::class)
        ->set('title', 'Test Product Created by E2E')
        ->set('handle', 'test-product-created-by-e2e')
        ->set('descriptionHtml', 'This product was created by the E2E test suite.')
        ->set('vendor', 'Test Vendor')
        ->set('productType', 'T-Shirts')
        ->set('variants.0.price', '29.99')
        ->set('variants.0.sku', 'E2E-TEST-001')
        ->set('variants.0.quantity', 50)
        ->call('save')
        ->assertSee('Product saved');

    $product = Product::query()->where('handle', 'test-product-created-by-e2e')->firstOrFail();

    expect($product->variants()->first()?->price_amount)->toBe(2999)
        ->and($product->variants()->first()?->inventoryItem?->quantity_on_hand)->toBe(50);

    Livewire::actingAs($user)
        ->test(ProductForm::class, ['product' => $product])
        ->set('title', 'Test Product Updated')
        ->call('save')
        ->assertSee('Product saved');

    Livewire::actingAs($user)
        ->test(ProductForm::class, ['product' => $product->refresh()])
        ->set('variants.0.sku', 'ACME-CTSH-S-WHT')
        ->call('save')
        ->assertHasErrors(['variants.0.sku']);

    expect($product->refresh()->variants()->first()?->sku)->toBe('E2E-TEST-001');

    $draftProduct = app(ProductService::class)->create($store, [
        'title' => 'Draft Product With Price Later',
        'handle' => 'draft-product-with-price-later',
        'status' => 'draft',
        'variants' => [[
            'sku' => 'DRAFT-PRICE-LATER-001',
            'price_amount' => 0,
            'currency' => $store->default_currency,
            'quantity_on_hand' => 4,
            'is_default' => true,
            'position' => 0,
        ]],
    ]);

    Livewire::actingAs($user)
        ->test(ProductForm::class, ['product' => $draftProduct])
        ->set('variants.0.price', '17.50')
        ->set('status', 'active')
        ->call('save')
        ->assertSee('Product saved');

    expect($draftProduct->refresh()->status->value)->toBe('active')
        ->and($draftProduct->variants()->first()?->price_amount)->toBe(1750);

    Livewire::actingAs($user)
        ->test(ProductForm::class, ['product' => $product->refresh()])
        ->set('status', 'archived')
        ->call('save')
        ->assertSee('Product saved');


sed -n '1,120p' tests/Feature/Storefront/ThemeDataTest.php

<?php

use App\Enums\PageStatus;
use App\Models\NavigationItem;
use App\Models\NavigationMenu;
use App\Models\Page;
use App\Models\Store;
use App\Models\Theme;
use App\Models\ThemeFile;
use App\Models\ThemeSettings;
use App\Services\NavigationService;
use App\Services\ThemeSettingsService;
use Database\Seeders\DatabaseSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Schema;

uses(RefreshDatabase::class);

beforeEach(function () {
    Cache::flush();
});

test('theme page and navigation tables and seeded fixtures exist', function () {
    $this->seed(DatabaseSeeder::class);

    expect(Schema::hasColumns('themes', ['store_id', 'name', 'version', 'status', 'published_at']))->toBeTrue()
        ->and(Schema::hasColumns('theme_files', ['theme_id', 'path', 'storage_key', 'sha256', 'byte_size']))->toBeTrue()
        ->and(Schema::hasColumns('theme_settings', ['theme_id', 'settings_json', 'updated_at']))->toBeTrue()
        ->and(Schema::hasColumns('pages', ['store_id', 'title', 'handle', 'body_html', 'status', 'published_at']))->toBeTrue()
        ->and(Schema::hasColumns('navigation_menus', ['store_id', 'handle', 'title']))->toBeTrue()
        ->and(Schema::hasColumns('navigation_items', ['menu_id', 'type', 'label', 'url', 'resource_id', 'position']))->toBeTrue()
        ->and(Theme::withoutGlobalScopes()->count())->toBe(2)
        ->and(ThemeFile::withoutGlobalScopes()->count())->toBe(6)
        ->and(ThemeSettings::withoutGlobalScopes()->count())->toBe(2)
        ->and(Page::withoutGlobalScopes()->count())->toBe(6)
        ->and(NavigationMenu::withoutGlobalScopes()->count())->toBe(4)
        ->and(NavigationItem::withoutGlobalScopes()->count())->toBeGreaterThanOrEqual(10);
});

test('theme page and navigation models are scoped to the resolved store', function () {
    $firstStore = Store::factory()->create();
    $secondStore = Store::factory()->create();

    Theme::factory()->published()->create(['store_id' => $firstStore->getKey(), 'name' => 'First Theme']);
    Theme::factory()->published()->create(['store_id' => $secondStore->getKey(), 'name' => 'Second Theme']);
    Page::factory()->published()->create(['store_id' => $firstStore->getKey(), 'handle' => 'first-page']);
    Page::factory()->published()->create(['store_id' => $secondStore->getKey(), 'handle' => 'second-page']);
    NavigationMenu::factory()->create(['store_id' => $firstStore->getKey(), 'handle' => 'first-menu']);
    NavigationMenu::factory()->create(['store_id' => $secondStore->getKey(), 'handle' => 'second-menu']);

    app()->instance('current_store', $firstStore);

    expect(Theme::query()->pluck('name')->all())->toBe(['First Theme'])
        ->and(Page::query()->pluck('handle')->all())->toBe(['first-page'])
        ->and(NavigationMenu::query()->pluck('handle')->all())->toBe(['first-menu']);
});

test('theme and navigation child models are scoped through their parent store', function () {
    $firstStore = Store::factory()->create();
    $secondStore = Store::factory()->create();
    $firstTheme = Theme::factory()->published()->create(['store_id' => $firstStore->getKey()]);
    $secondTheme = Theme::factory()->published()->create(['store_id' => $secondStore->getKey()]);
    $firstMenu = NavigationMenu::factory()->create(['store_id' => $firstStore->getKey()]);
    $secondMenu = NavigationMenu::factory()->create(['store_id' => $secondStore->getKey()]);

    ThemeFile::factory()->create(['theme_id' => $firstTheme->getKey(), 'path' => 'first.blade.php']);
    ThemeFile::factory()->create(['theme_id' => $secondTheme->getKey(), 'path' => 'second.blade.php']);
    ThemeSettings::factory()->create(['theme_id' => $firstTheme->getKey()]);
    ThemeSettings::factory()->create(['theme_id' => $secondTheme->getKey()]);
    NavigationItem::factory()->create(['menu_id' => $firstMenu->getKey(), 'label' => 'First']);
    NavigationItem::factory()->create(['menu_id' => $secondMenu->getKey(), 'label' => 'Second']);

    app()->instance('current_store', $firstStore);

    expect(ThemeFile::query()->pluck('path')->all())->toBe(['first.blade.php'])
        ->and(ThemeSettings::query()->count())->toBe(1)
        ->and(NavigationItem::query()->pluck('label')->all())->toBe(['First']);
});

test('theme settings service loads cached published settings with defaults', function () {
    $this->seed(DatabaseSeeder::class);

    $store = Store::query()->where('handle', 'acme-fashion')->firstOrFail();
    $settings = app(ThemeSettingsService::class)->forStore($store);

    expect($settings['announcement']['text'])->toBe('Free shipping on orders over 75.00 EUR')
        ->and($settings['home']['hero']['heading'])->toBe('Acme Fashion')
        ->and(Cache::has("theme_settings:{$store->getKey()}"))->toBeTrue();
});

test('navigation service resolves resource URLs for seeded menus', function () {
    $this->seed(DatabaseSeeder::class);

    $store = Store::query()->where('handle', 'acme-fashion')->firstOrFail();
    $menu = NavigationMenu::withoutGlobalScopes()
        ->where('store_id', $store->getKey())
        ->where('handle', 'main-menu')
        ->firstOrFail();

    $items = app(NavigationService::class)->buildTree($menu);

    expect(collect($items)->pluck('label')->all())->toContain('New Arrivals', 'T-Shirts', 'About')
        ->and(collect($items)->firstWhere('label', 'New Arrivals')['url'])->toBe('/collections/new-arrivals')
        ->and(collect($items)->firstWhere('label', 'About')['url'])->toBe('/pages/about');
});

test('storefront pages render published database content only', function () {
    $this->withoutVite();
    $this->seed(DatabaseSeeder::class);

    $store = Store::query()->where('handle', 'acme-fashion')->firstOrFail();

    Page::withoutGlobalScopes()->create([
        'store_id' => $store->getKey(),
        'title' => 'Private Draft',
        'handle' => 'private-draft',
        'body_html' => '
This should not render.
',
        'status' => PageStatus::Draft,
        'published_at' => null,


Output:
Chunk ID: 940c7a
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 1226
Output:
<?php

use App\Livewire\Admin\Collections\Form as CollectionForm;
use App\Livewire\Admin\Products\Form as ProductForm;
use App\Livewire\Admin\Products\Index as ProductIndex;
use App\Models\Collection;
use App\Models\Product;
use App\Models\Store;
use App\Models\User;
use App\Services\ProductService;
use Database\Seeders\DatabaseSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Livewire\Livewire;

uses(RefreshDatabase::class);

beforeEach(function () {
    $this->withoutVite();
    $this->seed(DatabaseSeeder::class);
});

test('storefront catalog browsing routes render seeded products', function () {
    $this->withHeader('Host', 'shop.test')
        ->get('/')
        ->assertSuccessful()
        ->assertSee('Acme Fashion')
        ->assertSee('Classic Cotton T-Shirt')
        ->assertSee('24.99 EUR');

    $this->withHeader('Host', 'shop.test')
        ->get('/collections/t-shirts')
        ->assertSuccessful()
        ->assertSee('T-Shirts')
        ->assertSee('Classic Cotton T-Shirt')
        ->assertDontSee('Unreleased Winter Jacket');

    $this->withHeader('Host', 'shop.test')
        ->get('/products/classic-cotton-t-shirt')
        ->assertSuccessful()
        ->assertSee('Classic Cotton T-Shirt')
        ->assertSee('Size')
        ->assertSee('Color')
        ->assertSee('Add to cart');

    $this->withHeader('Host', 'shop.test')
        ->get('/search?q=draft')
        ->assertSuccessful()
        ->assertDontSee('Unreleased Winter Jacket');
});

test('admin catalog routes require authentication and render for store admins', function () {
    $this->get('/admin/products')->assertRedirect('/admin/login');

    $user = User::query()->where('email', 'admin@acme.test')->firstOrFail();

    $this->actingAs($user)
        ->get('/admin/products')
        ->assertSuccessful()
        ->assertSee('Classic Cotton T-Shirt')
        ->assertSee('Premium Slim Fit Jeans');

    $this->actingAs($user)
        ->get('/admin/collections')
        ->assertSuccessful()
        ->assertSee('Collections')
        ->assertSee('T-Shirts');

    $this->actingAs($user)
        ->get('/admin/inventory')
        ->assertSuccessful()
        ->assertSee('Inventory')
        ->assertSee('Limited Edition Sneakers');
});

test('admin product form creates edits archives and index filters products', function () {
    $store = Store::query()->where('handle', 'acme-fashion')->firstOrFail();
    $user = User::query()->where('email', 'admin@acme.test')->firstOrFail();
    app()->instance('current_store', $store);

    Livewire::actingAs($user)
        ->test(ProductForm::class)
        ->set('title', 'Test Product Created by E2E')
        ->set('handle', 'test-product-created-by-e2e')
        ->set('descriptionHtml', 'This product was created by the E2E test suite.')
        ->set('vendor', 'Test Vendor')
        ->set('productType', 'T-Shirts')
        ->set('variants.0.price', '29.99')
        ->set('variants.0.sku', 'E2E-TEST-001')
        ->set('variants.0.quantity', 50)
        ->call('save')
        ->assertSee('Product saved');

    $product = Product::query()->where('handle', 'test-product-created-by-e2e')->firstOrFail();

    expect($product->variants()->first()?->price_amount)->toBe(2999)
        ->and($product->variants()->first()?->inventoryItem?->quantity_on_hand)->toBe(50);

    Livewire::actingAs($user)
        ->test(ProductForm::class, ['product' => $product])
        ->set('title', 'Test Product Updated')
        ->call('save')
        ->assertSee('Product saved');

    Livewire::actingAs($user)
        ->test(ProductForm::class, ['product' => $product->refresh()])
        ->set('variants.0.sku', 'ACME-CTSH-S-WHT')
        ->call('save')
        ->assertHasErrors(['variants.0.sku']);

    expect($product->refresh()->variants()->first()?->sku)->toBe('E2E-TEST-001');

    $draftProduct = app(ProductService::class)->create($store, [
        'title' => 'Draft Product With Price Later',
        'handle' => 'draft-product-with-price-later',
        'status' => 'draft',
        'variants' => [[
            'sku' => 'DRAFT-PRICE-LATER-001',
            'price_amount' => 0,
            'currency' => $store->default_currency,
            'quantity_on_hand' => 4,
            'is_default' => true,
            'position' => 0,
        ]],
    ]);

    Livewire::actingAs($user)
        ->test(ProductForm::class, ['product' => $draftProduct])
        ->set('variants.0.price', '17.50')
        ->set('status', 'active')
        ->call('save')
        ->assertSee('Product saved');

    expect($draftProduct->refresh()->status->value)->toBe('active')
        ->and($draftProduct->variants()->first()?->price_amount)->toBe(1750);

    Livewire::actingAs($user)
        ->test(ProductForm::class, ['product' => $product->refresh()])
        ->set('status', 'archived')
        ->call('save')
        ->assertSee('Product saved');


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

use App\Enums\PageStatus;
use App\Models\NavigationItem;
use App\Models\NavigationMenu;
use App\Models\Page;
use App\Models\Store;
use App\Models\Theme;
use App\Models\ThemeFile;
use App\Models\ThemeSettings;
use App\Services\NavigationService;
use App\Services\ThemeSettingsService;
use Database\Seeders\DatabaseSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Schema;

uses(RefreshDatabase::class);

beforeEach(function () {
    Cache::flush();
});

test('theme page and navigation tables and seeded fixtures exist', function () {
    $this->seed(DatabaseSeeder::class);

    expect(Schema::hasColumns('themes', ['store_id', 'name', 'version', 'status', 'published_at']))->toBeTrue()
        ->and(Schema::hasColumns('theme_files', ['theme_id', 'path', 'storage_key', 'sha256', 'byte_size']))->toBeTrue()
        ->and(Schema::hasColumns('theme_settings', ['theme_id', 'settings_json', 'updated_at']))->toBeTrue()
        ->and(Schema::hasColumns('pages', ['store_id', 'title', 'handle', 'body_html', 'status', 'published_at']))->toBeTrue()
        ->and(Schema::hasColumns('navigation_menus', ['store_id', 'handle', 'title']))->toBeTrue()
        ->and(Schema::hasColumns('navigation_items', ['menu_id', 'type', 'label', 'url', 'resource_id', 'position']))->toBeTrue()
        ->and(Theme::withoutGlobalScopes()->count())->toBe(2)
        ->and(ThemeFile::withoutGlobalScopes()->count())->toBe(6)
        ->and(ThemeSettings::withoutGlobalScopes()->count())->toBe(2)
        ->and(Page::withoutGlobalScopes()->count())->toBe(6)
        ->and(NavigationMenu::withoutGlobalScopes()->count())->toBe(4)
        ->and(NavigationItem::withoutGlobalScopes()->count())->toBeGreaterThanOrEqual(10);
});

test('theme page and navigation models are scoped to the resolved store', function () {
    $firstStore = Store::factory()->create();
    $secondStore = Store::factory()->create();

    Theme::factory()->published()->create(['store_id' => $firstStore->getKey(), 'name' => 'First Theme']);
    Theme::factory()->published()->create(['store_id' => $secondStore->getKey(), 'name' => 'Second Theme']);
    Page::factory()->published()->create(['store_id' => $firstStore->getKey(), 'handle' => 'first-page']);
    Page::factory()->published()->create(['store_id' => $secondStore->getKey(), 'handle' => 'second-page']);
    NavigationMenu::factory()->create(['store_id' => $firstStore->getKey(), 'handle' => 'first-menu']);
    NavigationMenu::factory()->create(['store_id' => $secondStore->getKey(), 'handle' => 'second-menu']);

    app()->instance('current_store', $firstStore);

    expect(Theme::query()->pluck('name')->all())->toBe(['First Theme'])
        ->and(Page::query()->pluck('handle')->all())->toBe(['first-page'])
        ->and(NavigationMenu::query()->pluck('handle')->all())->toBe(['first-menu']);
});

test('theme and navigation child models are scoped through their parent store', function () {
    $firstStore = Store::factory()->create();
    $secondStore = Store::factory()->create();
    $firstTheme = Theme::factory()->published()->create(['store_id' => $firstStore->getKey()]);
    $secondTheme = Theme::factory()->published()->create(['store_id' => $secondStore->getKey()]);
    $firstMenu = NavigationMenu::factory()->create(['store_id' => $firstStore->getKey()]);
    $secondMenu = NavigationMenu::factory()->create(['store_id' => $secondStore->getKey()]);

    ThemeFile::factory()->create(['theme_id' => $firstTheme->getKey(), 'path' => 'first.blade.php']);
    ThemeFile::factory()->create(['theme_id' => $secondTheme->getKey(), 'path' => 'second.blade.php']);
    ThemeSettings::factory()->create(['theme_id' => $firstTheme->getKey()]);
    ThemeSettings::factory()->create(['theme_id' => $secondTheme->getKey()]);
    NavigationItem::factory()->create(['menu_id' => $firstMenu->getKey(), 'label' => 'First']);
    NavigationItem::factory()->create(['menu_id' => $secondMenu->getKey(), 'label' => 'Second']);

    app()->instance('current_store', $firstStore);

    expect(ThemeFile::query()->pluck('path')->all())->toBe(['first.blade.php'])
        ->and(ThemeSettings::query()->count())->toBe(1)
        ->and(NavigationItem::query()->pluck('label')->all())->toBe(['First']);
});

test('theme settings service loads cached published settings with defaults', function () {
    $this->seed(DatabaseSeeder::class);

    $store = Store::query()->where('handle', 'acme-fashion')->firstOrFail();
    $settings = app(ThemeSettingsService::class)->forStore($store);

    expect($settings['announcement']['text'])->toBe('Free shipping on orders over 75.00 EUR')
        ->and($settings['home']['hero']['heading'])->toBe('Acme Fashion')
        ->and(Cache::has("theme_settings:{$store->getKey()}"))->toBeTrue();
});

test('navigation service resolves resource URLs for seeded menus', function () {
    $this->seed(DatabaseSeeder::class);

    $store = Store::query()->where('handle', 'acme-fashion')->firstOrFail();
    $menu = NavigationMenu::withoutGlobalScopes()
        ->where('store_id', $store->getKey())
        ->where('handle', 'main-menu')
        ->firstOrFail();

    $items = app(NavigationService::class)->buildTree($menu);

    expect(collect($items)->pluck('label')->all())->toContain('New Arrivals', 'T-Shirts', 'About')
        ->and(collect($items)->firstWhere('label', 'New Arrivals')['url'])->toBe('/collections/new-arrivals')
        ->and(collect($items)->firstWhere('label', 'About')['url'])->toBe('/pages/about');
});

test('storefront pages render published database content only', function () {
    $this->withoutVite();
    $this->seed(DatabaseSeeder::class);

    $store = Store::query()->where('handle', 'acme-fashion')->firstOrFail();

    Page::withoutGlobalScopes()->create([
        'store_id' => $store->getKey(),
        'title' => 'Private Draft',
        'handle' => 'private-draft',
        'body_html' => '
This should not render.
',
        'status' => PageStatus::Draft,
        'published_at' => null,


sed -n '390,435p' specs/09-IMPLEMENTATION-ROADMAP.md


sed -n '588,604p' specs/09-IMPLEMENTATION-ROADMAP.md


sed -n '1278,1338p' specs/09-IMPLEMENTATION-ROADMAP.md


sed -n '1630,1665p' specs/09-IMPLEMENTATION-ROADMAP.md


sed -n '390,435p' specs/09-IMPLEMENTATION-ROADMAP.md

| `ThemeStatus` | `Draft`, `Published` |
| `PageStatus` | `Draft`, `Published`, `Archived` |
| `NavigationItemType` | `Link`, `Page`, `Collection`, `Product` |

#### Step 3.3: Storefront Blade Layout

**Directory structure for `resources/views/storefront/`:**

| Directory | Files |
|-----------|-------|
| `layouts/` | `app.blade.php` |
| `pages/` | `home.blade.php`, `collection.blade.php`, `product.blade.php`, `cart.blade.php`, `search.blade.php`, `page.blade.php` |
| `checkout/` | `index.blade.php`, `confirmation.blade.php` |
| `account/` | `login.blade.php`, `register.blade.php`, `dashboard.blade.php` |
| `account/orders/` | `index.blade.php`, `show.blade.php` |
| `account/addresses/` | `index.blade.php` |
| `errors/` | `404.blade.php`, `503.blade.php` |
| `components/` | `product-card.blade.php`, `price.blade.php`, `badge.blade.php`, `quantity-selector.blade.php`, `address-form.blade.php`, `order-summary.blade.php`, `breadcrumbs.blade.php`, `pagination.blade.php` |

Base layout (`app.blade.php`): header with nav, announcement bar, main content, footer, cart drawer. All with dark mode support via `dark:` prefix.

**ThemeSettings Service:** Singleton registered in `AppServiceProvider`, loads and caches active theme settings for the current store.

#### Step 3.4: Storefront Livewire Components

**Files to create:**

| Component | Purpose |
|-----------|---------|
| `Storefront\Home` | Dynamic sections based on theme settings |
| `Storefront\Collections\Index` | All collections grid |
| `Storefront\Collections\Show` | Product grid with filters/sort/pagination |
| `Storefront\Products\Show` | Variant selection, image gallery, add-to-cart |
| `Storefront\Cart\Show` | Full cart page with line items and totals |
| `Storefront\CartDrawer` | Slide-out cart drawer (always in layout) |
| `Storefront\Search\Index` | Search results with filters |
| `Storefront\Search\Modal` | Search modal with autocomplete |
| `Storefront\Pages\Show` | Static CMS page rendering |

#### Step 3.5: Navigation Service

**File to create:** `App\Services\NavigationService`

| Method | Description |
|--------|-------------|
| `buildTree(NavigationMenu $menu): array` | Builds navigation trees from navigation_menus / navigation_items |


sed -n '588,604p' specs/09-IMPLEMENTATION-ROADMAP.md

**Files to create:**

| Component | Description |
|-----------|-------------|
| `Storefront\CartDrawer` | Slide-out panel: line items, quantity controls, discount code input, subtotal, checkout button |
| `Storefront\Cart\Show` | Full cart page with same functionality plus shipping estimate |
| `Storefront\Checkout\Show` | Multi-step: Contact/Address -> Shipping method -> Payment. Stepper UI. |
| `Storefront\Checkout\Confirmation` | Order confirmation: order number, items, totals, next steps |

---

### Phase 5: Payments, Orders, Fulfillment

**Priority:** HIGH - completes the purchase flow.

#### Step 5.1: Migrations (Batch 5-7)


Output:
Chunk ID: 3ecdae
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 560
Output:
| `ThemeStatus` | `Draft`, `Published` |
| `PageStatus` | `Draft`, `Published`, `Archived` |
| `NavigationItemType` | `Link`, `Page`, `Collection`, `Product` |

#### Step 3.3: Storefront Blade Layout

**Directory structure for `resources/views/storefront/`:**

| Directory | Files |
|-----------|-------|
| `layouts/` | `app.blade.php` |
| `pages/` | `home.blade.php`, `collection.blade.php`, `product.blade.php`, `cart.blade.php`, `search.blade.php`, `page.blade.php` |
| `checkout/` | `index.blade.php`, `confirmation.blade.php` |
| `account/` | `login.blade.php`, `register.blade.php`, `dashboard.blade.php` |
| `account/orders/` | `index.blade.php`, `show.blade.php` |
| `account/addresses/` | `index.blade.php` |
| `errors/` | `404.blade.php`, `503.blade.php` |
| `components/` | `product-card.blade.php`, `price.blade.php`, `badge.blade.php`, `quantity-selector.blade.php`, `address-form.blade.php`, `order-summary.blade.php`, `breadcrumbs.blade.php`, `pagination.blade.php` |

Base layout (`app.blade.php`): header with nav, announcement bar, main content, footer, cart drawer. All with dark mode support via `dark:` prefix.

**ThemeSettings Service:** Singleton registered in `AppServiceProvider`, loads and caches active theme settings for the current store.

#### Step 3.4: Storefront Livewire Components

**Files to create:**

| Component | Purpose |
|-----------|---------|
| `Storefront\Home` | Dynamic sections based on theme settings |
| `Storefront\Collections\Index` | All collections grid |
| `Storefront\Collections\Show` | Product grid with filters/sort/pagination |
| `Storefront\Products\Show` | Variant selection, image gallery, add-to-cart |
| `Storefront\Cart\Show` | Full cart page with line items and totals |
| `Storefront\CartDrawer` | Slide-out cart drawer (always in layout) |
| `Storefront\Search\Index` | Search results with filters |
| `Storefront\Search\Modal` | Search modal with autocomplete |
| `Storefront\Pages\Show` | Static CMS page rendering |

#### Step 3.5: Navigation Service

**File to create:** `App\Services\NavigationService`

| Method | Description |
|--------|-------------|
| `buildTree(NavigationMenu $menu): array` | Builds navigation trees from navigation_menus / navigation_items |


Output:
Chunk ID: 609f78
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 160
Output:
**Files to create:**

| Component | Description |
|-----------|-------------|
| `Storefront\CartDrawer` | Slide-out panel: line items, quantity controls, discount code input, subtotal, checkout button |
| `Storefront\Cart\Show` | Full cart page with same functionality plus shipping estimate |
| `Storefront\Checkout\Show` | Multi-step: Contact/Address -> Shipping method -> Payment. Stepper UI. |
| `Storefront\Checkout\Confirmation` | Order confirmation: order number, items, totals, next steps |

---

### Phase 5: Payments, Orders, Fulfillment

**Priority:** HIGH - completes the purchase flow.

#### Step 5.1: Migrations (Batch 5-7)


sed -n '1278,1338p' specs/09-IMPLEMENTATION-ROADMAP.md

#### tests/Feature/Cart/CartServiceTest.php

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

---

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

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

---

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

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

---

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

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

---


sed -n '1630,1665p' specs/09-IMPLEMENTATION-ROADMAP.md

---

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

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

---

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

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

---

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