Output:
Chunk ID: ecc6de
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 2581
Output:
     1

     2

     3

     4	            <flux:heading size="xl">{{ $isEditing ? 'Edit discount' : 'Create discount' }}</flux:heading>
     5	            <flux:text class="mt-1">Configure the promotion type, value, eligibility, limits, and dates.</flux:text>
     6

     7
     8	        <flux:button :href="route('admin.discounts.index')" wire:navigate variant="filled" icon="arrow-left">
     9	            Discounts
    10	        </flux:button>
    11

    12
    13	    @if (session('status'))
    14	        <flux:callout color="green" icon="check-circle">{{ session('status') }}</flux:callout>
    15	    @endif
    16
    17	    <form wire:submit="save" class="space-y-6">
    18

    19	            <flux:radio.group wire:model.live="type" label="Type" class="grid gap-3 sm:grid-cols-2">
    20	                <flux:radio value="code" label="Discount code" description="Customers enter a code at checkout" />
    21	                <flux:radio value="automatic" label="Automatic discount" description="Applied automatically at checkout" />
    22	            </flux:radio.group>
    23	            <flux:error name="type" />
    24

    25
    26	        @if ($type === 'code')
    27

    28	                <flux:heading size="lg">Code</flux:heading>
    29

    30

    31	                        <flux:input wire:model="code" label="Discount code" placeholder="SUMMER20" />
    32	                        <flux:error name="code" />
    33

    34

    35	                        <flux:button type="button" wire:click="generateCode" variant="filled">Generate</flux:button>
    36

    37

    38

    39	        @endif
    40
    41

    42	            <flux:heading size="lg">Value</flux:heading>
    43
    44

    45	                <flux:radio.group wire:model.live="valueType" label="Value type" class="grid gap-3 sm:grid-cols-3">
    46	                    <flux:radio value="percent" label="Percentage" />
    47	                    <flux:radio value="fixed" label="Fixed amount" />
    48	                    <flux:radio value="free_shipping" label="Free shipping" />
    49	                </flux:radio.group>
    50
    51	                @if ($valueType !== 'free_shipping')
    52

    53	                        <flux:input wire:model="valueAmount" type="number" step="0.01" min="0" label="{{ $valueType === 'percent' ? 'Percentage' : 'Amount' }}" />
    54	                        <flux:error name="valueAmount" />
    55

    56	                @endif
    57

    58

    59
    60

    61	            <flux:heading size="lg">Conditions</flux:heading>
    62
    63

    64

    65	                    <flux:input wire:model="minimumPurchaseAmount" type="number" step="0.01" min="0" label="Minimum purchase amount" placeholder="0.00" />
    66	                    <flux:error name="minimumPurchaseAmount" />
    67

    68
    69

    70

    71	                        <flux:input wire:model.live.debounce.300ms="productSearch" icon="magnifying-glass" label="Specific products" placeholder="Search products..." />
    72
    73	                        @if ($productResults->isNotEmpty())
    74

    75	                                @foreach ($productResults as $product)
    76	                                    <button type="button" wire:click="addProduct({{ $product->getKey() }})" wire:key="discount-product-result-{{ $product->getKey() }}" class="block w-full px-3 py-2 text-left text-sm hover:bg-zinc-50 dark:hover:bg-zinc-800">
    77	                                        {{ $product->title }}
    78	                                    </button>
    79	                                @endforeach
    80

    81	                        @endif
    82
    83

    84	                            @foreach ($selectedProducts as $product)
    85
getKey() }}" class="flex items-center justify-between gap-3 rounded-lg border border-zinc-200 px-3 py-2 text-sm dark:border-zinc-700">
    86	                                    {{ $product->title }}
    87	                                    <flux:button type="button" wire:click="removeProduct({{ $product->getKey() }})" size="sm" variant="ghost" icon="x-mark" aria-label="Remove {{ $product->title }}" />
    88

    89	                            @endforeach
    90

    91

    92
    93

    94	                        <flux:input wire:model.live.debounce.300ms="collectionSearch" icon="magnifying-glass" label="Specific collections" placeholder="Search collections..." />
    95
    96	                        @if ($collectionResults->isNotEmpty())
    97

    98	                                @foreach ($collectionResults as $collection)
    99	                                    <button type="button" wire:click="addCollection({{ $collection->getKey() }})" wire:key="discount-collection-result-{{ $collection->getKey() }}" class="block w-full px-3 py-2 text-left text-sm hover:bg-zinc-50 dark:hover:bg-zinc-800">
   100	                                        {{ $collection->title }}
   101	                                    </button>
   102	                                @endforeach
   103

   104	                        @endif
   105
   106

   107	                            @foreach ($selectedCollections as $collection)
   108
getKey() }}" class="flex items-center justify-between gap-3 rounded-lg border border-zinc-200 px-3 py-2 text-sm dark:border-zinc-700">
   109	                                    {{ $collection->title }}
   110	                                    <flux:button type="button" wire:click="removeCollection({{ $collection->getKey() }})" size="sm" variant="ghost" icon="x-mark" aria-label="Remove {{ $collection->title }}" />
   111

   112	                            @endforeach
   113

   114

   115

   116

   117

   118
   119

   120	            <flux:heading size="lg">Usage limits</flux:heading>
   121
   122

   123	                <flux:input wire:model="usageLimit" type="number" min="1" label="Total usage limit" placeholder="Unlimited" />
   124

   125	                    <flux:checkbox wire:model="onePerCustomer" label="Limit to one use per customer" />
   126

   127

   128	            <flux:error name="usageLimit" />
   129

   130
   131

   132	            <flux:heading size="lg">Active dates</flux:heading>
   133
   134

   135	                <flux:input wire:model="startsAt" type="datetime-local" label="Start date" />
   136	                <flux:input wire:model="endsAt" type="datetime-local" label="End date" />
   137

   138

   139	                <flux:error name="startsAt" />
   140	                <flux:error name="endsAt" />
   141

   142

   143
   144

   145	            <flux:checkbox wire:model="isActive" label="{{ $isActive ? 'Active' : 'Disabled' }}" />
   146

   147
   148

   149

   150	                <flux:button :href="route('admin.discounts.index')" wire:navigate variant="ghost">Discard</flux:button>
   151	                <flux:button type="submit" variant="primary" wire:loading.attr="disabled">
   152	                    Save
   153	                    Saving...
   154	                </flux:button>
   155

   156

   157	    </form>
   158


Output:
Chunk ID: 300f74
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 1250
Output:
     1	<?php
     2
     3	namespace App\Providers;
     4
     5	use App\Auth\CustomerUserProvider;
     6	use App\Contracts\PaymentProvider;
     7	use App\Http\Middleware\CheckStoreRole;
     8	use App\Http\Middleware\ResolveStore;
     9	use App\Models\Store;
    10	use App\Services\NavigationService;
    11	use App\Services\Payments\MockPaymentProvider;
    12	use App\Services\ThemeSettingsService;
    13	use Carbon\CarbonImmutable;
    14	use Illuminate\Auth\Middleware\Authenticate;
    15	use Illuminate\Cache\RateLimiting\Limit;
    16	use Illuminate\Http\Request;
    17	use Illuminate\Support\Facades\Auth;
    18	use Illuminate\Support\Facades\Date;
    19	use Illuminate\Support\Facades\DB;
    20	use Illuminate\Support\Facades\RateLimiter;
    21	use Illuminate\Support\Facades\View;
    22	use Illuminate\Support\ServiceProvider;
    23	use Illuminate\Validation\Rules\Password;
    24	use Illuminate\View\View as ViewInstance;
    25	use Livewire\Livewire;
    26
    27	class AppServiceProvider extends ServiceProvider
    28	{
    29	    /**
    30	     * Register any application services.
    31	     */
    32	    public function register(): void
    33	    {
    34	        $this->app->bind(PaymentProvider::class, MockPaymentProvider::class);
    35
    36	        Auth::provider('store_scoped_eloquent', function ($app, array $config): CustomerUserProvider {
    37	            return new CustomerUserProvider($app['hash'], $config['model']);
    38	        });
    39
    40	        $this->app->singleton(ThemeSettingsService::class);
    41	        $this->app->singleton(NavigationService::class);
    42	    }
    43
    44	    /**
    45	     * Bootstrap any application services.
    46	     */
    47	    public function boot(): void
    48	    {
    49	        $this->configureDefaults();
    50	        $this->configureLivewireMiddleware();
    51	        $this->configureStorefrontViewData();
    52	    }
    53
    54	    /**
    55	     * Configure default behaviors for production-ready applications.
    56	     */
    57	    protected function configureDefaults(): void
    58	    {
    59	        Date::use(CarbonImmutable::class);
    60
    61	        DB::prohibitDestructiveCommands(
    62	            app()->isProduction(),
    63	        );
    64
    65	        Password::defaults(fn (): ?Password => app()->isProduction()
    66	            ? Password::min(12)
    67	                ->mixedCase()
    68	                ->letters()
    69	                ->numbers()
    70	                ->symbols()
    71	                ->uncompromised()
    72	            : null
    73	        );
    74
    75	        RateLimiter::for('api.storefront', function (Request $request): Limit {
    76	            return Limit::perMinute(120)->by($request->ip());
    77	        });
    78
    79	        RateLimiter::for('checkout', function (Request $request): Limit {
    80	            $sessionId = $request->hasSession() ? $request->session()->getId() : null;
    81
    82	            return Limit::perMinute(10)->by($sessionId ?: $request->ip());
    83	        });
    84
    85	        Authenticate::redirectUsing(function (Request $request): string {
    86	            if ($request->is('admin*')) {
    87	                return route('admin.login');
    88	            }
    89
    90	            if ($request->is('account*')) {
    91	                return route('account.login');
    92	            }
    93
    94	            return route('login');
    95	        });
    96	    }
    97
    98	    protected function configureStorefrontViewData(): void
    99	    {
   100	        View::composer('layouts.storefront', function (ViewInstance $view): void {
   101	            $store = app()->bound('current_store') ? app('current_store') : null;
   102
   103	            if (! $store instanceof Store) {
   104	                $view->with([
   105	                    'themeSettings' => [],
   106	                    'mainNavigation' => [],
   107	                    'footerNavigation' => [],
   108	                ]);
   109
   110	                return;
   111	            }
   112
   113	            $themeSettings = app(ThemeSettingsService::class)->forStore($store);
   114	            $navigation = app(NavigationService::class);
   115
   116	            $view->with([
   117	                'themeSettings' => $themeSettings,
   118	                'mainNavigation' => $navigation->forHandle($store, data_get($themeSettings, 'header.main_menu', 'main-menu')),
   119	                'footerNavigation' => $navigation->forHandle($store, data_get($themeSettings, 'footer.menu', 'footer-menu')),
   120	            ]);
   121	        });
   122	    }
   123
   124	    protected function configureLivewireMiddleware(): void
   125	    {
   126	        Livewire::addPersistentMiddleware([
   127	            ResolveStore::class,
   128	            CheckStoreRole::class,
   129	        ]);
   130	    }
   131	}


Output:
Chunk ID: d70568
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 54
Output:
 app/Providers/AppServiceProvider.php          | 12 ++++++++++++
 resources/views/layouts/app/sidebar.blade.php |  4 ++++
 routes/web.php                                |  5 +++++
 3 files changed, 21 insertions(+)


rg "discount" specs/03-ADMIN-UI.md specs/05-BUSINESS-LOGIC.md specs/09-IMPLEMENTATION-ROADMAP.md -n


rg "one_per_customer|usage|minimum|percentage|percent" specs/01-DATABASE-SCHEMA.md specs/05-BUSINESS-LOGIC.md -n


rg "one_per_customer|usage|minimum|percentage|percent" specs/01-DATABASE-SCHEMA.md specs/05-BUSINESS-LOGIC.md -n

rg 'one_per_customer|usage|minimum|percentage|percent' specs/01-DATABASE-SCHEMA.md specs/05-BUSINESS-LOGIC.md -n

Question: one_per_customer|usage|minimum|percentage|percent

specs/01-DATABASE-SCHEMA.md:682:| value_type | TEXT | No | - | CHECK IN ('fixed', 'percent', 'free_shipping') | Discount calculation type |
specs/01-DATABASE-SCHEMA.md:686:| usage_limit | INTEGER | Yes | NULL | - | Max redemptions; null = unlimited |
specs/01-DATABASE-SCHEMA.md:687:| usage_count | INTEGER | No | 0 | - | Current redemption count |
specs/01-DATABASE-SCHEMA.md:700:**Notes:** `value_amount` interpretation depends on `value_type`. For `percent`, it is a whole number (e.g. 15 = 15%). For `fixed`, it is in minor units (cents). For `free_shipping`, it is ignored. `rules_json` may contain minimum purchase amounts, applicable product/collection IDs, customer eligibility, etc.
specs/05-BUSINESS-LOGIC.md:12:| Percentage storage | Basis points where noted (e.g., 1900 = 19.00%) or whole-number percentage in `discounts.value_amount` (e.g., 15 = 15%) |
specs/05-BUSINESS-LOGIC.md:563:       INCREMENT discount's usage_count by 1
specs/05-BUSINESS-LOGIC.md:620:| `active` | Live and applicable to carts (subject to date range and usage limits). | Automatic transition when `starts_at` is reached (if set). Admin can manually activate. |
specs/05-BUSINESS-LOGIC.md:632:**Enum:** `App\Enums\DiscountValueType` - values: `percent`, `fixed`, `free_shipping`
specs/05-BUSINESS-LOGIC.md:636:| `percent` | `discount = applicable_subtotal * value_amount / 100` | `value_amount` stores whole percentage (e.g., 15 = 15%) |
specs/05-BUSINESS-LOGIC.md:646:| `min_purchase_amount` | integer (cents) or null | Cart subtotal must be >= this value. Null means no minimum. |
specs/05-BUSINESS-LOGIC.md:670:    H -->|Yes| J{usage_limit set AND usage_count >= usage_limit?}
specs/05-BUSINESS-LOGIC.md:671:    J -->|Yes| K["Error: discount_usage_limit_reached"]
specs/05-BUSINESS-LOGIC.md:672:    J -->|No| L{min_purchase_amount set AND subtotal < minimum?}
specs/05-BUSINESS-LOGIC.md:686:| `discount_usage_limit_reached` | `usage_count >= usage_limit` |
specs/05-BUSINESS-LOGIC.md:692:For `percent` and `fixed` discounts with applicable product restrictions:
specs/05-BUSINESS-LOGIC.md:700:    IF discount.value_type = 'percent':
specs/05-BUSINESS-LOGIC.md:725:- Increment `discounts.usage_count` by 1.
specs/05-BUSINESS-LOGIC.md:1301:| `password` | Required, minimum 8 characters |
specs/05-BUSINESS-LOGIC.md:1679:| DiscountValueType | Percent (`percent`), Fixed (`fixed`), FreeShipping (`free_shipping`) |


rg "discount" specs/03-ADMIN-UI.md specs/05-BUSINESS-LOGIC.md specs/09-IMPLEMENTATION-ROADMAP.md -n

rg discount specs/03-ADMIN-UI.md specs/05-BUSINESS-LOGIC.md specs/09-IMPLEMENTATION-ROADMAP.md -n

Question: discount

specs/05-BUSINESS-LOGIC.md:12:| Percentage storage | Basis points where noted (e.g., 1900 = 19.00%) or whole-number percentage in `discounts.value_amount` (e.g., 15 = 15%) |
specs/05-BUSINESS-LOGIC.md:80:products, collections, customers, orders, carts, checkouts, discounts, shipping_zones, themes, pages, navigation_menus, analytics_events, analytics_daily, webhook_subscriptions, inventory_items, search_queries.
specs/05-BUSINESS-LOGIC.md:94:| Manage discounts | Yes | Yes | Yes | No |
specs/05-BUSINESS-LOGIC.md:300:8. Set `line_discount_amount = 0` (recalculated by discount engine during checkout).
specs/05-BUSINESS-LOGIC.md:301:9. Calculate: `line_total_amount = line_subtotal_amount - line_discount_amount`.
specs/05-BUSINESS-LOGIC.md:330:line_discount_amount = 0  (updated by discount engine during checkout)
specs/05-BUSINESS-LOGIC.md:331:line_total_amount    = line_subtotal_amount - line_discount_amount
specs/05-BUSINESS-LOGIC.md:361:    Apply discount rules (Section 7)
specs/05-BUSINESS-LOGIC.md:364:    subtotal - total_discount"]
specs/05-BUSINESS-LOGIC.md:368:    Calculated on discounted line amounts
specs/05-BUSINESS-LOGIC.md:371:    discounted_subtotal + shipping + tax_total"]
specs/05-BUSINESS-LOGIC.md:380:| `subtotal` | integer (cents) | Sum of all line subtotals before discount |
specs/05-BUSINESS-LOGIC.md:381:| `discount` | integer (cents) | Total discount amount |
specs/05-BUSINESS-LOGIC.md:385:| `total` | integer (cents) | Final amount: discounted_subtotal + shipping + taxTotal |
specs/05-BUSINESS-LOGIC.md:398:- Each line's discount allocation, tax, and total are rounded independently.
specs/05-BUSINESS-LOGIC.md:401:- Any remainder from proportional discount allocation is added to the last qualifying line (largest-remainder method).
specs/05-BUSINESS-LOGIC.md:406:- Recalculate on every significant state change: address change (may affect tax/shipping zones), shipping method change, discount code change.
specs/05-BUSINESS-LOGIC.md:550:       - tax_lines_json, discount_allocations_json
specs/05-BUSINESS-LOGIC.md:562:    9. IF checkout has a discount code:
specs/05-BUSINESS-LOGIC.md:563:       INCREMENT discount's usage_count by 1
specs/05-BUSINESS-LOGIC.md:608:| `code` | Customer enters a code at checkout. Stored in `discounts.code`, matched case-insensitively. |
specs/05-BUSINESS-LOGIC.md:612:- Only one code discount per checkout.
specs/05-BUSINESS-LOGIC.md:613:- Multiple automatic discounts can stack (each applied sequentially to the remaining undiscounted amount).
specs/05-BUSINESS-LOGIC.md:619:| `draft` | Created but not yet active. Not visible to customers, not applicable to carts. | Admin creates discount -> `draft`. Admin activates -> `active`. |
specs/05-BUSINESS-LOGIC.md:622:| `disabled` | Manually deactivated by admin. Not applicable to carts. | Admin disables an `active` discount. Admin can re-enable -> `active`. |
specs/05-BUSINESS-LOGIC.md:636:| `percent` | `discount = applicable_subtotal * value_amount / 100` | `value_amount` stores whole percentage (e.g., 15 = 15%) |
specs/05-BUSINESS-LOGIC.md:637:| `fixed` | `discount = value_amount` (cents) | Cannot exceed the applicable subtotal |
specs/05-BUSINESS-LOGIC.md:638:| `free_shipping` | Override shipping amount to 0 | No monetary discount on items. `value_amount` is ignored. |
specs/05-BUSINESS-LOGIC.md:642:Rules are stored in `discounts.rules_json` with the following structure:
specs/05-BUSINESS-LOGIC.md:651:**Applicability logic:** When both `applicable_product_ids` and `applicable_collection_ids` are set, a product qualifies if it matches **either** condition (union, not intersection). When both are null or empty, the discount applies to the entire order.
specs/05-BUSINESS-LOGIC.md:661:    A[Input: discount code + cart] --> B{Code exists in store?
specs/05-BUSINESS-LOGIC.md:663:    B -->|No| C["Error: discount_not_found"]
specs/05-BUSINESS-LOGIC.md:665:    D -->|No| E["Error: discount_expired"]
specs/05-BUSINESS-LOGIC.md:667:    F -->|No| G["Error: discount_not_yet_active"]
specs/05-BUSINESS-LOGIC.md:669:    H -->|No| I["Error: discount_expired"]
specs/05-BUSINESS-LOGIC.md:671:    J -->|Yes| K["Error: discount_usage_limit_reached"]
specs/05-BUSINESS-LOGIC.md:673:    L -->|Yes| M["Error: discount_min_purchase_not_met"]
specs/05-BUSINESS-LOGIC.md:675:    N -->|Yes| O["Error: discount_not_applicable"]
specs/05-BUSINESS-LOGIC.md:676:    N -->|No| P["Valid - return discount"]
specs/05-BUSINESS-LOGIC.md:683:| `discount_not_found` | Code does not exist in this store |
specs/05-BUSINESS-LOGIC.md:684:| `discount_expired` | Past `ends_at` date or status is not active |
specs/05-BUSINESS-LOGIC.md:685:| `discount_not_yet_active` | Before `starts_at` date |
specs/05-BUSINESS-LOGIC.md:686:| `discount_usage_limit_reached` | `usage_count >= usage_limit` |
specs/05-BUSINESS-LOGIC.md:687:| `discount_min_purchase_not_met` | Cart subtotal < `min_purchase_amount` |
specs/05-BUSINESS-LOGIC.md:688:| `discount_not_applicable` | No qualifying products in car…8562 chars truncated…ulates | apply 10% discount to 10000 subtotal | discount=1000, discounted subtotal=9000 | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1356:| applies a valid percent discount code at checkout | discount: 10%, code "SAVE10"; apply to checkout with subtotal 5000 | discount = 500 | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1357:| applies a valid fixed discount code at checkout | discount: 500 fixed, code "5OFF" | discount = 500 | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1358:| removes discount when code is cleared | apply discount, then clear code | discount = 0, totals recalculated | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1359:| rejects expired discount at checkout | discount ended yesterday | error returned | validation |
specs/09-IMPLEMENTATION-ROADMAP.md:1360:| increments usage count on order completion | discount usage_count=5; complete checkout with discount | usage_count=6 | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1361:| handles free shipping discount at checkout | apply free_shipping discount | shipping = 0 in totals | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1381:| calculates exclusive tax correctly at checkout | tax mode: manual, rate: 1900, prices_include_tax: false; discounted subtotal + shipping = 5499 | tax = 1044 (rounded) | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1544:| lists discounts | create 3 discounts; GET /admin/discounts | sees all 3 | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1545:| creates a percent discount | Livewire: code="SAVE10", type=percent, value=10 | discount in DB | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1546:| creates a fixed discount | Livewire: code="5OFF", type=fixed, value=500 | discount in DB | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1547:| validates discount code uniqueness within store | create discount with code "SAVE10"; attempt to create another with same code | validation error | validation |
specs/09-IMPLEMENTATION-ROADMAP.md:1548:| edits a discount | change value from 10 to 15 | updated in DB | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1549:| disables a discount | set status to disabled | status = disabled | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1655:| applies a discount code | POST /api/storefront/v1/checkouts/{id}/apply-discount with code | 200 response, discount applied in totals | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1721:| `Discount` | Discount code or automatic discount |
specs/09-IMPLEMENTATION-ROADMAP.md:1758:| `Admin/Discounts/Form` | Shared discount form (create/edit mode) |
specs/09-IMPLEMENTATION-ROADMAP.md:1827:| `StoreDiscountRequest` | Validate discount creation |
specs/09-IMPLEMENTATION-ROADMAP.md:1828:| `UpdateDiscountRequest` | Validate discount update |
specs/09-IMPLEMENTATION-ROADMAP.md:1899:| `Checkout/DiscountTest` | Checkout discount feature tests |
specs/09-IMPLEMENTATION-ROADMAP.md:1913:| `Admin/DiscountManagementTest` | Admin discount management feature tests |
specs/09-IMPLEMENTATION-ROADMAP.md:1969:| `PricingEngine` | Deterministic pricing pipeline: subtotal -> discount -> shipping -> tax -> total |
specs/03-ADMIN-UI.md:90:  Discounts                      -> admin.discounts.index
specs/03-ADMIN-UI.md:1237:**Route:** `GET /admin/discounts`
specs/03-ADMIN-UI.md:1241:**Blade view:** `resources/views/livewire/admin/discounts/index.blade.php`
specs/03-ADMIN-UI.md:1247:| `search` | `string` | Search by discount code |
specs/03-ADMIN-UI.md:1252:| `discounts` | Paginated, filtered discounts |
specs/03-ADMIN-UI.md:1256:- Heading "Discounts" with "Create discount" primary button
specs/03-ADMIN-UI.md:1272:**Route:** `GET /admin/discounts/create` and `GET /admin/discounts/{discount}/edit`
specs/03-ADMIN-UI.md:1276:**Blade view:** `resources/views/livewire/admin/discounts/form.blade.php`
specs/03-ADMIN-UI.md:1282:| `discount` | `?Discount` | The discount being edited |
specs/03-ADMIN-UI.md:1288:| `specificProductIds` | `array<int>` | Specific products the discount applies to |
specs/03-ADMIN-UI.md:1289:| `specificCollectionIds` | `array<int>` | Specific collections the discount applies to |
specs/03-ADMIN-UI.md:1294:| `isActive` | `bool` | Whether the discount is active |
specs/03-ADMIN-UI.md:1300:| `save()` | Validates and saves the discount |
specs/03-ADMIN-UI.md:1301:| `generateCode()` | Auto-generates a random discount code |
specs/03-ADMIN-UI.md:1313:|  (o) Automatic discount - Applied automatically at checkout      |
specs/03-ADMIN-UI.md:1353:**Type section:** Two radio buttons - "Discount code" (description: "Customers enter a code at checkout") and "Automatic discount" (description: "Applied automatically at checkout"). Selecting type is live-bound.
specs/03-ADMIN-UI.md:2414:| `App\Livewire\Admin\Discounts\Index` | `livewire.admin.discounts.index` | `GET /admin/discounts` |
specs/03-ADMIN-UI.md:2415:| `App\Livewire\Admin\Discounts\Form` | `livewire.admin.discounts.form` | `GET /admin/discounts/create`, `GET /admin/discounts/{discount}/edit` |


Output:
Chunk ID: e53090
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 4641
Output:
specs/05-BUSINESS-LOGIC.md:12:| Percentage storage | Basis points where noted (e.g., 1900 = 19.00%) or whole-number percentage in `discounts.value_amount` (e.g., 15 = 15%) |
specs/05-BUSINESS-LOGIC.md:80:products, collections, customers, orders, carts, checkouts, discounts, shipping_zones, themes, pages, navigation_menus, analytics_events, analytics_daily, webhook_subscriptions, inventory_items, search_queries.
specs/05-BUSINESS-LOGIC.md:94:| Manage discounts | Yes | Yes | Yes | No |
specs/05-BUSINESS-LOGIC.md:300:8. Set `line_discount_amount = 0` (recalculated by discount engine during checkout).
specs/05-BUSINESS-LOGIC.md:301:9. Calculate: `line_total_amount = line_subtotal_amount - line_discount_amount`.
specs/05-BUSINESS-LOGIC.md:330:line_discount_amount = 0  (updated by discount engine during checkout)
specs/05-BUSINESS-LOGIC.md:331:line_total_amount    = line_subtotal_amount - line_discount_amount
specs/05-BUSINESS-LOGIC.md:361:    Apply discount rules (Section 7)
specs/05-BUSINESS-LOGIC.md:364:    subtotal - total_discount"]
specs/05-BUSINESS-LOGIC.md:368:    Calculated on discounted line amounts
specs/05-BUSINESS-LOGIC.md:371:    discounted_subtotal + shipping + tax_total"]
specs/05-BUSINESS-LOGIC.md:380:| `subtotal` | integer (cents) | Sum of all line subtotals before discount |
specs/05-BUSINESS-LOGIC.md:381:| `discount` | integer (cents) | Total discount amount |
specs/05-BUSINESS-LOGIC.md:385:| `total` | integer (cents) | Final amount: discounted_subtotal + shipping + taxTotal |
specs/05-BUSINESS-LOGIC.md:398:- Each line's discount allocation, tax, and total are rounded independently.
specs/05-BUSINESS-LOGIC.md:401:- Any remainder from proportional discount allocation is added to the last qualifying line (largest-remainder method).
specs/05-BUSINESS-LOGIC.md:406:- Recalculate on every significant state change: address change (may affect tax/shipping zones), shipping method change, discount code change.
specs/05-BUSINESS-LOGIC.md:550:       - tax_lines_json, discount_allocations_json
specs/05-BUSINESS-LOGIC.md:562:    9. IF checkout has a discount code:
specs/05-BUSINESS-LOGIC.md:563:       INCREMENT discount's usage_count by 1
specs/05-BUSINESS-LOGIC.md:608:| `code` | Customer enters a code at checkout. Stored in `discounts.code`, matched case-insensitively. |
specs/05-BUSINESS-LOGIC.md:612:- Only one code discount per checkout.
specs/05-BUSINESS-LOGIC.md:613:- Multiple automatic discounts can stack (each applied sequentially to the remaining undiscounted amount).
specs/05-BUSINESS-LOGIC.md:619:| `draft` | Created but not yet active. Not visible to customers, not applicable to carts. | Admin creates discount -> `draft`. Admin activates -> `active`. |
specs/05-BUSINESS-LOGIC.md:622:| `disabled` | Manually deactivated by admin. Not applicable to carts. | Admin disables an `active` discount. Admin can re-enable -> `active`. |
specs/05-BUSINESS-LOGIC.md:636:| `percent` | `discount = applicable_subtotal * value_amount / 100` | `value_amount` stores whole percentage (e.g., 15 = 15%) |
specs/05-BUSINESS-LOGIC.md:637:| `fixed` | `discount = value_amount` (cents) | Cannot exceed the applicable subtotal |
specs/05-BUSINESS-LOGIC.md:638:| `free_shipping` | Override shipping amount to 0 | No monetary discount on items. `value_amount` is ignored. |
specs/05-BUSINESS-LOGIC.md:642:Rules are stored in `discounts.rules_json` with the following structure:
specs/05-BUSINESS-LOGIC.md:651:**Applicability logic:** When both `applicable_product_ids` and `applicable_collection_ids` are set, a product qualifies if it matches **either** condition (union, not intersection). When both are null or empty, the discount applies to the entire order.
specs/05-BUSINESS-LOGIC.md:661:    A[Input: discount code + cart] --> B{Code exists in store?
specs/05-BUSINESS-LOGIC.md:663:    B -->|No| C["Error: discount_not_found"]
specs/05-BUSINESS-LOGIC.md:665:    D -->|No| E["Error: discount_expired"]
specs/05-BUSINESS-LOGIC.md:667:    F -->|No| G["Error: discount_not_yet_active"]
specs/05-BUSINESS-LOGIC.md:669:    H -->|No| I["Error: discount_expired"]
specs/05-BUSINESS-LOGIC.md:671:    J -->|Yes| K["Error: discount_usage_limit_reached"]
specs/05-BUSINESS-LOGIC.md:673:    L -->|Yes| M["Error: discount_min_purchase_not_met"]
specs/05-BUSINESS-LOGIC.md:675:    N -->|Yes| O["Error: discount_not_applicable"]
specs/05-BUSINESS-LOGIC.md:676:    N -->|No| P["Valid - return discount"]
specs/05-BUSINESS-LOGIC.md:683:| `discount_not_found` | Code does not exist in this store |
specs/05-BUSINESS-LOGIC.md:684:| `discount_expired` | Past `ends_at` date or status is not active |
specs/05-BUSINESS-LOGIC.md:685:| `discount_not_yet_active` | Before `starts_at` date |
specs/05-BUSINESS-LOGIC.md:686:| `discount_usage_limit_reached` | `usage_count >= usage_limit` |
specs/05-BUSINESS-LOGIC.md:687:| `discount_min_purchase_not_met` | Cart subtotal < `min_purchase_amount` |
specs/05-BUSINESS-LOGIC.md:688:| `discount_not_applicable` | No qualifying products in cart |
specs/05-BUSINESS-LOGIC.md:692:For `percent` and `fixed` discounts with applicable product restrictions:
specs/05-BUSINESS-LOGIC.md:695:PROCEDURE allocateDiscount(discount, cartLines):
specs/05-BUSINESS-LOGIC.md:700:    IF discount.value_type = 'percent':
specs/05-BUSINESS-LOGIC.md:701:        totalDiscount = qualifyingSubtotal * discount.value_amount / 100
specs/05-BUSINESS-LOGIC.md:702:    ELSE IF discount.value_type = 'fixed':
specs/05-BUSINESS-LOGIC.md:703:        totalDiscount = MIN(discount.value_amount, qualifyingSubtotal)
specs/05-BUSINESS-LOGIC.md:710:        SET line.line_discount_amount = lineDiscount
specs/05-BUSINESS-LOGIC.md:713:    SET lastLine.line_discount_amount = remainingDiscount
specs/05-BUSINESS-LOGIC.md:719:- During checkout: update `cart_lines.line_discount_amount`.
specs/05-BUSINESS-LOGIC.md:720:- On order creation: store in `order_lines.discount_allocations_json` as an array of `{discount_id, amount}`.
specs/05-BUSINESS-LOGIC.md:724:When an order is successfully created with a discount code:
specs/05-BUSINESS-LOGIC.md:725:- Increment `discounts.usage_count` by 1.
specs/05-BUSINESS-LOGIC.md:737:- The TaxCalculationRequest contains: line items (with discounted amounts), shipping amount, shipping address, store tax settings.
specs/05-BUSINESS-LOGIC.md:756:line_tax = ROUND(line_total_after_discount * rate / 10000)
specs/05-BUSINESS-LOGIC.md:811:Discounts are always applied before tax calculation. Tax is calculated on the discounted amount. This aligns with standard accounting practice and Stripe Tax behavior.
specs/05-BUSINESS-LOGIC.md:813:For tax-inclusive pricing (`prices_include_tax = true`): The discount is subtracted from the gross (tax-inclusive) subtotal. Tax is then extracted from the post-discount gross amount: `tax = post_discount_gross * rate / (1 + rate)`. This means the tax portion decreases proportionally when a discount is applied.
specs/05-BUSINESS-LOGIC.md:817:2. Apply discount allocations to lines.
specs/05-BUSINESS-LOGIC.md:818:3. Calculate tax on discounted line amounts.
specs/05-BUSINESS-LOGIC.md:923:If a validated discount has `value_type = 'free_shipping'`:
specs/05-BUSINESS-LOGIC.md:1283:**CSV columns:** `order_number`, `created_at`, `status`, `financial_status`, `fulfillment_status`, `customer_email`, `customer_name`, `subtotal_amount`, `discount_amount`, `shipping_amount`, `tax_amount`, `total_amount`, `currency`, `shipping_method`, `tracking_number`
specs/05-BUSINESS-LOGIC.md:1700:| PricingResult | subtotal (int), discount (int), shipping (int), taxLines (array of TaxLine), taxTotal (int), total (int), currency (string) | Complete pricing calculation output |
specs/05-BUSINESS-LOGIC.md:1704:| DiscountValidationResult | valid (bool), discount (nullable), errorCode (nullable string), errorMessage (nullable string) | Discount validation outcome |
specs/09-IMPLEMENTATION-ROADMAP.md:176:Applied on: products, collections, customers, orders, carts, checkouts, discounts, shipping_zones, themes, pages, navigation_menus, analytics_events, analytics_daily, webhook_subscriptions, inventory_items, search_queries.
specs/09-IMPLEMENTATION-ROADMAP.md:235:| Manage discounts | Y | Y | Y | N |
specs/09-IMPLEMENTATION-ROADMAP.md:454:7. `create_discounts_table` - FK: `store_id`
specs/09-IMPLEMENTATION-ROADMAP.md:468:| Model (with migration, factory, seeder) | `Discount` | Discount code or automatic discount |
specs/09-IMPLEMENTATION-ROADMAP.md:516:| `calculate(Discount $discount, int $subtotal, array $lines): DiscountResult` | Calculates amount, allocates proportionally across qualifying lines |
specs/09-IMPLEMENTATION-ROADMAP.md:553:4. Discounted subtotal: subtotal minus discount
specs/09-IMPLEMENTATION-ROADMAP.md:555:6. Tax: on discounted amounts (plus shipping if taxable)
specs/09-IMPLEMENTATION-ROADMAP.md:556:7. Total: discounted_subtotal + shipping + tax_total
specs/09-IMPLEMENTATION-ROADMAP.md:564:| Value Object | `App\ValueObjects\PricingResult` | subtotal, discount, shipping, taxLines, taxTotal, total, currency |
specs/09-IMPLEMENTATION-ROADMAP.md:592:| `Storefront\CartDrawer` | Slide-out panel: line items, quantity controls, discount code input, subtotal, checkout button |
specs/09-IMPLEMENTATION-ROADMAP.md:839:| `Admin\Discounts\Form` | Shared form component for create/edit discount: code, type, value, dates, rules |
specs/09-IMPLEMENTATION-ROADMAP.md:1020:| applies percent discount correctly | subtotal 10000, discount 10% (value_amount = 10, value_type = percent) | discount = 1000, discounted subtotal = 9000 | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1021:| applies fixed discount correctly | subtotal 10000, discount 500 cents fixed | discount = 500, discounted subtotal = 9500 | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1022:| caps fixed discount at subtotal so it never goes negative | subtotal 300, discount 500 cents fixed | discount = 300, discounted subtotal = 0 | edge case |
specs/09-IMPLEMENTATION-ROADMAP.md:1023:| applies free shipping discount by zeroing shipping | subtotal 5000, shipping 499, discount type free_shipping | shipping = 0 | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1028:| calculates full checkout totals end to end | 2 lines x 2499 = 4998, discount WELCOME10 (10%), shipping flat 499, tax 19% inclusive; pipeline: subtotal 4998 -> discount 499 -> discounted 4499 -> shipping 499 -> tax extracted from gross | total calculation matches expected value | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1029:| handles rounding correctly with odd cent amounts | 3 lines with prices that create fractional discount allocations | sum of rounded allocations equals total discount (no off-by-one) | edge case |
specs/09-IMPLEMENTATION-ROADMAP.md:1040:| validates an active discount code | discount: status=active, starts_at=yesterday, ends_at=tomorrow | validation passes, returns Discount model | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1041:| rejects an expired discount code | discount: status=active, ends_at=yesterday | throws InvalidDiscountException with reason 'expired' | validation |
specs/09-IMPLEMENTATION-ROADMAP.md:1042:| rejects a not-yet-active discount code | discount: status=active, starts_at=tomorrow | throws InvalidDiscountException with reason 'not_yet_active' | validation |
specs/09-IMPLEMENTATION-ROADMAP.md:1043:| rejects a discount that has reached its usage limit | discount: usage_limit=10, usage_count=10 | throws InvalidDiscountException with reason 'usage_limit_reached' | validation |
specs/09-IMPLEMENTATION-ROADMAP.md:1044:| rejects an unknown discount code | look up code 'DOESNOTEXIST' | throws InvalidDiscountException with reason 'not_found' | validation |
specs/09-IMPLEMENTATION-ROADMAP.md:1045:| performs case-insensitive code lookup | discount with code 'SUMMER20'; validate code 'summer20' | validation passes | edge case |
specs/09-IMPLEMENTATION-ROADMAP.md:1046:| enforces minimum purchase amount rule | discount with rules_json containing minimum_purchase: 5000; cart subtotal: 3000 | throws InvalidDiscountException with reason 'minimum_not_met' | validation |
specs/09-IMPLEMENTATION-ROADMAP.md:1047:| passes minimum purchase when cart meets threshold | discount with rules_json minimum_purchase: 5000; cart subtotal: 5000 | validation passes | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1048:| calculates percent discount amount | discount: 15%, subtotal: 10000 | discount amount = 1500 | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1049:| calculates fixed discount amount | discount: 500 cents fixed, subtotal: 10000 | discount amount = 500 | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1050:| handles free shipping discount type | discount: type=free_shipping | discount amount = 0, free_shipping flag = true | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1051:| allocates discount proportionally across multiple lines | 2 lines: line A subtotal 7500, line B subtotal 2500 (total 10000); discount: 10% = 1000 | line A allocation = 750, line B allocation = 250 | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1052:| distributes rounding remainder to the last qualifying line | 3 lines with subtotals that create uneven division | sum of allocations exactly equals total discount | edge case |
specs/09-IMPLEMENTATION-ROADMAP.md:1344:| calculates correct totals for a simple checkout | 1 item: 2500 x 2 = 5000, no discount, flat shipping 499, 19% tax exclusive | subtotal=5000, shipping=499, tax=1044 (19% of 5499), total=6543 | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1345:| applies discount code and recalculates | apply 10% discount to 10000 subtotal | discount=1000, discounted subtotal=9000 | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1356:| applies a valid percent discount code at checkout | discount: 10%, code "SAVE10"; apply to checkout with subtotal 5000 | discount = 500 | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1357:| applies a valid fixed discount code at checkout | discount: 500 fixed, code "5OFF" | discount = 500 | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1358:| removes discount when code is cleared | apply discount, then clear code | discount = 0, totals recalculated | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1359:| rejects expired discount at checkout | discount ended yesterday | error returned | validation |
specs/09-IMPLEMENTATION-ROADMAP.md:1360:| increments usage count on order completion | discount usage_count=5; complete checkout with discount | usage_count=6 | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1361:| handles free shipping discount at checkout | apply free_shipping discount | shipping = 0 in totals | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1381:| calculates exclusive tax correctly at checkout | tax mode: manual, rate: 1900, prices_include_tax: false; discounted subtotal + shipping = 5499 | tax = 1044 (rounded) | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1544:| lists discounts | create 3 discounts; GET /admin/discounts | sees all 3 | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1545:| creates a percent discount | Livewire: code="SAVE10", type=percent, value=10 | discount in DB | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1546:| creates a fixed discount | Livewire: code="5OFF", type=fixed, value=500 | discount in DB | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1547:| validates discount code uniqueness within store | create discount with code "SAVE10"; attempt to create another with same code | validation error | validation |
specs/09-IMPLEMENTATION-ROADMAP.md:1548:| edits a discount | change value from 10 to 15 | updated in DB | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1549:| disables a discount | set status to disabled | status = disabled | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1655:| applies a discount code | POST /api/storefront/v1/checkouts/{id}/apply-discount with code | 200 response, discount applied in totals | happy path |
specs/09-IMPLEMENTATION-ROADMAP.md:1721:| `Discount` | Discount code or automatic discount |
specs/09-IMPLEMENTATION-ROADMAP.md:1758:| `Admin/Discounts/Form` | Shared discount form (create/edit mode) |
specs/09-IMPLEMENTATION-ROADMAP.md:1827:| `StoreDiscountRequest` | Validate discount creation |
specs/09-IMPLEMENTATION-ROADMAP.md:1828:| `UpdateDiscountRequest` | Validate discount update |
specs/09-IMPLEMENTATION-ROADMAP.md:1899:| `Checkout/DiscountTest` | Checkout discount feature tests |
specs/09-IMPLEMENTATION-ROADMAP.md:1913:| `Admin/DiscountManagementTest` | Admin discount management feature tests |
specs/09-IMPLEMENTATION-ROADMAP.md:1969:| `PricingEngine` | Deterministic pricing pipeline: subtotal -> discount -> shipping -> tax -> total |
specs/03-ADMIN-UI.md:90:  Discounts                      -> admin.discounts.index
specs/03-ADMIN-UI.md:1237:**Route:** `GET /admin/discounts`
specs/03-ADMIN-UI.md:1241:**Blade view:** `resources/views/livewire/admin/discounts/index.blade.php`
specs/03-ADMIN-UI.md:1247:| `search` | `string` | Search by discount code |
specs/03-ADMIN-UI.md:1252:| `discounts` | Paginated, filtered discounts |
specs/03-ADMIN-UI.md:1256:- Heading "Discounts" with "Create discount" primary button
specs/03-ADMIN-UI.md:1272:**Route:** `GET /admin/discounts/create` and `GET /admin/discounts/{discount}/edit`
specs/03-ADMIN-UI.md:1276:**Blade view:** `resources/views/livewire/admin/discounts/form.blade.php`
specs/03-ADMIN-UI.md:1282:| `discount` | `?Discount` | The discount being edited |
specs/03-ADMIN-UI.md:1288:| `specificProductIds` | `array<int>` | Specific products the discount applies to |
specs/03-ADMIN-UI.md:1289:| `specificCollectionIds` | `array<int>` | Specific collections the discount applies to |
specs/03-ADMIN-UI.md:1294:| `isActive` | `bool` | Whether the discount is active |
specs/03-ADMIN-UI.md:1300:| `save()` | Validates and saves the discount |
specs/03-ADMIN-UI.md:1301:| `generateCode()` | Auto-generates a random discount code |
specs/03-ADMIN-UI.md:1313:|  (o) Automatic discount - Applied automatically at checkout      |
specs/03-ADMIN-UI.md:1353:**Type section:** Two radio buttons - "Discount code" (description: "Customers enter a code at checkout") and "Automatic discount" (description: "Applied automatically at checkout"). Selecting type is live-bound.
specs/03-ADMIN-UI.md:2414:| `App\Livewire\Admin\Discounts\Index` | `livewire.admin.discounts.index` | `GET /admin/discounts` |
specs/03-ADMIN-UI.md:2415:| `App\Livewire\Admin\Discounts\Form` | `livewire.admin.discounts.form` | `GET /admin/discounts/create`, `GET /admin/discounts/{discount}/edit` |


Output:
Chunk ID: 7e637d
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 601
Output:
specs/01-DATABASE-SCHEMA.md:682:| value_type | TEXT | No | - | CHECK IN ('fixed', 'percent', 'free_shipping') | Discount calculation type |
specs/01-DATABASE-SCHEMA.md:686:| usage_limit | INTEGER | Yes | NULL | - | Max redemptions; null = unlimited |
specs/01-DATABASE-SCHEMA.md:687:| usage_count | INTEGER | No | 0 | - | Current redemption count |
specs/01-DATABASE-SCHEMA.md:700:**Notes:** `value_amount` interpretation depends on `value_type`. For `percent`, it is a whole number (e.g. 15 = 15%). For `fixed`, it is in minor units (cents). For `free_shipping`, it is ignored. `rules_json` may contain minimum purchase amounts, applicable product/collection IDs, customer eligibility, etc.
specs/05-BUSINESS-LOGIC.md:12:| Percentage storage | Basis points where noted (e.g., 1900 = 19.00%) or whole-number percentage in `discounts.value_amount` (e.g., 15 = 15%) |
specs/05-BUSINESS-LOGIC.md:563:       INCREMENT discount's usage_count by 1
specs/05-BUSINESS-LOGIC.md:620:| `active` | Live and applicable to carts (subject to date range and usage limits). | Automatic transition when `starts_at` is reached (if set). Admin can manually activate. |
specs/05-BUSINESS-LOGIC.md:632:**Enum:** `App\Enums\DiscountValueType` - values: `percent`, `fixed`, `free_shipping`
specs/05-BUSINESS-LOGIC.md:636:| `percent` | `discount = applicable_subtotal * value_amount / 100` | `value_amount` stores whole percentage (e.g., 15 = 15%) |
specs/05-BUSINESS-LOGIC.md:646:| `min_purchase_amount` | integer (cents) or null | Cart subtotal must be >= this value. Null means no minimum. |
specs/05-BUSINESS-LOGIC.md:670:    H -->|Yes| J{usage_limit set AND usage_count >= usage_limit?}
specs/05-BUSINESS-LOGIC.md:671:    J -->|Yes| K["Error: discount_usage_limit_reached"]
specs/05-BUSINESS-LOGIC.md:672:    J -->|No| L{min_purchase_amount set AND subtotal < minimum?}
specs/05-BUSINESS-LOGIC.md:686:| `discount_usage_limit_reached` | `usage_count >= usage_limit` |
specs/05-BUSINESS-LOGIC.md:692:For `percent` and `fixed` discounts with applicable product restrictions:
specs/05-BUSINESS-LOGIC.md:700:    IF discount.value_type = 'percent':
specs/05-BUSINESS-LOGIC.md:725:- Increment `discounts.usage_count` by 1.
specs/05-BUSINESS-LOGIC.md:1301:| `password` | Required, minimum 8 characters |
specs/05-BUSINESS-LOGIC.md:1679:| DiscountValueType | Percent (`percent`), Fixed (`fixed`), FreeShipping (`free_shipping`) |


sed -n '1230,1365p' specs/03-ADMIN-UI.md


sed -n '1230,1365p' specs/03-ADMIN-UI.md

---

## 10. Discounts List and Form

### 10.1 Discounts List

**Route:** `GET /admin/discounts`

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

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

#### Component Requirements

| Property | Type | Purpose |
|----------|------|---------|
| `search` | `string` | Search by discount code |
| `statusFilter` | `string` | `all`, `active`, `expired`, `scheduled` |

| Computed Property | Purpose |
|-------------------|---------|
| `discounts` | Paginated, filtered discounts |

#### Layout

- Heading "Discounts" with "Create discount" primary button
- Search input and status filter

#### Table Columns

| Column | Content |
|--------|---------|
| Code | Discount code or "Automatic" label |
| Type | Flux badge: "Code" or "Automatic" |
| Value | e.g., "10%" or "$5.00" or "Free shipping" |
| Usage | "3 / 100" (used / limit) or "3 / unlimited" |
| Status | Flux badge: active=green, expired=red, scheduled=yellow |
| Dates | Start and end dates |

### 10.2 Discount Form

**Route:** `GET /admin/discounts/create` and `GET /admin/discounts/{discount}/edit`

**Livewire component:** `App\Livewire\Admin\Discounts\Form`

**Blade view:** `resources/views/livewire/admin/discounts/form.blade.php`

#### Component Requirements

| Property | Type | Purpose |
|----------|------|---------|
| `discount` | `?Discount` | The discount being edited |
| `type` | `string` | `code` or `automatic` |
| `code` | `string` | Discount code (if type is code) |
| `valueType` | `string` | `percent`, `fixed`, `free_shipping` |
| `valueAmount` | `?int` | Discount value (percentage or fixed amount) |
| `minimumPurchaseAmount` | `?int` | Minimum cart value for eligibility |
| `specificProductIds` | `array<int>` | Specific products the discount applies to |
| `specificCollectionIds` | `array<int>` | Specific collections the discount applies to |
| `usageLimit` | `?int` | Maximum total uses |
| `onePerCustomer` | `bool` | Limit to one use per customer |
| `startsAt` | `string` | Start datetime |
| `endsAt` | `?string` | End datetime (optional) |
| `isActive` | `bool` | Whether the discount is active |
| `productSearch` | `string` | Search for products to add |
| `collectionSearch` | `string` | Search for collections to add |

| Action | Behavior |
|--------|----------|
| `save()` | Validates and saves the discount |
| `generateCode()` | Auto-generates a random discount code |
| `addProduct(int $productId)` | Adds product to specific products list |
| `removeProduct(int $productId)` | Removes product |
| `addCollection(int $collectionId)` | Adds collection to specific collections |
| `removeCollection(int $collectionId)` | Removes collection |

#### Layout Wireframe (Single Column)

```
+==================================================================+
| TYPE CARD                                                         |
|  ( ) Discount code - Customers enter a code at checkout          |
|  (o) Automatic discount - Applied automatically at checkout      |
+==================================================================+
| CODE CARD (shown when type = "code")                             |
|  Discount code: [SUMMER20             ]  [Generate]              |
+==================================================================+
| VALUE CARD                                                        |
|  ( ) Percentage                                                   |
|  (o) Fixed amount                                                 |
|  ( ) Free shipping                                                |
|                                                                   |
|  Amount: [5.00                        ] (hidden for free shipping)|
+==================================================================+
| CONDITIONS CARD                                                   |
|  Minimum purchase amount: [0.00       ]                          |
|  (Leave empty for no minimum)                                     |
|                                                                   |
|  Specific products:                                               |
|  [Search products...          ]                                   |
|  +---results dropdown---+                                         |
|  Selected: Blue Shirt [x]  Red Hat [x]                           |
|                                                                   |
|  Specific collections: (same pattern as products)                 |
+==================================================================+
| USAGE LIMITS CARD                                                 |
|  Total usage limit: [100              ] (or leave blank)         |
|  [x] Limit to one use per customer                               |
+==================================================================+
| ACTIVE DATES CARD                                                 |
|  Start date: [2024-01-15 00:00       ]                           |
|  End date:   [2024-02-15 00:00       ] (or leave blank)          |
+==================================================================+
| STATUS                                                            |
|  [switch: ON]  Active                                             |
+==================================================================+
| STICKY SAVE BAR                          [Discard]  [Save]       |
+==================================================================+
```

#### Section Details

**Type section:** Two radio buttons - "Discount code" (description: "Customers enter a code at checkout") and "Automatic discount" (description: "Applied automatically at checkout"). Selecting type is live-bound.

**Code input:** Shown only when type = "code". Text input with placeholder "SUMMER20" and a "Generate" ghost button that auto-generates a random code.

**Value section:** Three radio buttons for value type: Percentage, Fixed amount, Free shipping. When not free shipping, a number input appears for the amount (label changes based on type: "Percentage" or "Amount").

**Conditions section:**
- Minimum purchase amount: number input with description "Leave empty for no minimum"
- Specific products: search input with live debounce. Results appear in a scrollable dropdown. Selected products shown as a list with remove (X) buttons.
- Specific collections: same pattern as products.

**Usage limits section:**
- Total usage limit number input (placeholder: "Unlimited")


Output:
Chunk ID: 8fe9f4
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 1622
Output:

---

## 10. Discounts List and Form

### 10.1 Discounts List

**Route:** `GET /admin/discounts`

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

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

#### Component Requirements

| Property | Type | Purpose |
|----------|------|---------|
| `search` | `string` | Search by discount code |
| `statusFilter` | `string` | `all`, `active`, `expired`, `scheduled` |

| Computed Property | Purpose |
|-------------------|---------|
| `discounts` | Paginated, filtered discounts |

#### Layout

- Heading "Discounts" with "Create discount" primary button
- Search input and status filter

#### Table Columns

| Column | Content |
|--------|---------|
| Code | Discount code or "Automatic" label |
| Type | Flux badge: "Code" or "Automatic" |
| Value | e.g., "10%" or "$5.00" or "Free shipping" |
| Usage | "3 / 100" (used / limit) or "3 / unlimited" |
| Status | Flux badge: active=green, expired=red, scheduled=yellow |
| Dates | Start and end dates |

### 10.2 Discount Form

**Route:** `GET /admin/discounts/create` and `GET /admin/discounts/{discount}/edit`

**Livewire component:** `App\Livewire\Admin\Discounts\Form`

**Blade view:** `resources/views/livewire/admin/discounts/form.blade.php`

#### Component Requirements

| Property | Type | Purpose |
|----------|------|---------|
| `discount` | `?Discount` | The discount being edited |
| `type` | `string` | `code` or `automatic` |
| `code` | `string` | Discount code (if type is code) |
| `valueType` | `string` | `percent`, `fixed`, `free_shipping` |
| `valueAmount` | `?int` | Discount value (percentage or fixed amount) |
| `minimumPurchaseAmount` | `?int` | Minimum cart value for eligibility |
| `specificProductIds` | `array<int>` | Specific products the discount applies to |
| `specificCollectionIds` | `array<int>` | Specific collections the discount applies to |
| `usageLimit` | `?int` | Maximum total uses |
| `onePerCustomer` | `bool` | Limit to one use per customer |
| `startsAt` | `string` | Start datetime |
| `endsAt` | `?string` | End datetime (optional) |
| `isActive` | `bool` | Whether the discount is active |
| `productSearch` | `string` | Search for products to add |
| `collectionSearch` | `string` | Search for collections to add |

| Action | Behavior |
|--------|----------|
| `save()` | Validates and saves the discount |
| `generateCode()` | Auto-generates a random discount code |
| `addProduct(int $productId)` | Adds product to specific products list |
| `removeProduct(int $productId)` | Removes product |
| `addCollection(int $collectionId)` | Adds collection to specific collections |
| `removeCollection(int $collectionId)` | Removes collection |

#### Layout Wireframe (Single Column)

```
+==================================================================+
| TYPE CARD                                                         |
|  ( ) Discount code - Customers enter a code at checkout          |
|  (o) Automatic discount - Applied automatically at checkout      |
+==================================================================+
| CODE CARD (shown when type = "code")                             |
|  Discount code: [SUMMER20             ]  [Generate]              |
+==================================================================+
| VALUE CARD                                                        |
|  ( ) Percentage                                                   |
|  (o) Fixed amount                                                 |
|  ( ) Free shipping                                                |
|                                                                   |
|  Amount: [5.00                        ] (hidden for free shipping)|
+==================================================================+
| CONDITIONS CARD                                                   |
|  Minimum purchase amount: [0.00       ]                          |
|  (Leave empty for no minimum)                                     |
|                                                                   |
|  Specific products:                                               |
|  [Search products...          ]                                   |
|  +---results dropdown---+                                         |
|  Selected: Blue Shirt [x]  Red Hat [x]                           |
|                                                                   |
|  Specific collections: (same pattern as products)                 |
+==================================================================+
| USAGE LIMITS CARD                                                 |
|  Total usage limit: [100              ] (or leave blank)         |
|  [x] Limit to one use per customer                               |
+==================================================================+
| ACTIVE DATES CARD                                                 |
|  Start date: [2024-01-15 00:00       ]                           |
|  End date:   [2024-02-15 00:00       ] (or leave blank)          |
+==================================================================+
| STATUS                                                            |
|  [switch: ON]  Active                                             |
+==================================================================+
| STICKY SAVE BAR                          [Discard]  [Save]       |
+==================================================================+
```

#### Section Details

**Type section:** Two radio buttons - "Discount code" (description: "Customers enter a code at checkout") and "Automatic discount" (description: "Applied automatically at checkout"). Selecting type is live-bound.

**Code input:** Shown only when type = "code". Text input with placeholder "SUMMER20" and a "Generate" ghost button that auto-generates a random code.

**Value section:** Three radio buttons for value type: Percentage, Fixed amount, Free shipping. When not free shipping, a number input appears for the amount (label changes based on type: "Percentage" or "Amount").

**Conditions section:**
- Minimum purchase amount: number input with description "Leave empty for no minimum"
- Specific products: search input with live debounce. Results appear in a scrollable dropdown. Selected products shown as a list with remove (X) buttons.
- Specific collections: same pattern as products.

**Usage limits section:**
- Total usage limit number input (placeholder: "Unlimited")


sed -n '1365,1415p' specs/03-ADMIN-UI.md


sed -n '1365,1415p' specs/03-ADMIN-UI.md

- Total usage limit number input (placeholder: "Unlimited")
- "Limit to one use per customer" checkbox

**Active dates section:**
- Start date: datetime-local input
- End date: datetime-local input with description "Leave empty for no end date"

**Status:** Toggle switch with text label "Active" or "Disabled".

Sticky save bar at the bottom (same pattern as product form).

---

## 11. Settings Pages

### 11.1 General Settings

**Route:** `GET /admin/settings`

**Livewire component:** `App\Livewire\Admin\Settings\General`

**Blade view:** `resources/views/livewire/admin/settings/general.blade.php`

#### Component Requirements

| Property | Type | Purpose |
|----------|------|---------|
| `storeName` | `string` | Store display name |
| `storeHandle` | `string` | Store handle (read-only or with warning) |
| `defaultCurrency` | `string` | Currency code (e.g., "EUR") |
| `defaultLocale` | `string` | Locale code (e.g., "en") |
| `timezone` | `string` | Timezone string |

| Action | Behavior |
|--------|----------|
| `save()` | Validates and saves store settings |

#### Layout

Two-column layout per section: left side has section label and description, right side has form fields.

**Store details section:**
- Left: Heading "Store details" + description "Basic information about your store."
- Right: Store name input, Store handle input (disabled, with description: "The store handle cannot be changed after creation.")

Separator between sections.

**Defaults section:**
- Left: Heading "Defaults" + description "Currency, language, and timezone settings."
- Right:
  - Default currency: Select (EUR/USD/GBP and more)


Output:
Chunk ID: a6fd6b
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 408
Output:
- Total usage limit number input (placeholder: "Unlimited")
- "Limit to one use per customer" checkbox

**Active dates section:**
- Start date: datetime-local input
- End date: datetime-local input with description "Leave empty for no end date"

**Status:** Toggle switch with text label "Active" or "Disabled".

Sticky save bar at the bottom (same pattern as product form).

---

## 11. Settings Pages

### 11.1 General Settings

**Route:** `GET /admin/settings`

**Livewire component:** `App\Livewire\Admin\Settings\General`

**Blade view:** `resources/views/livewire/admin/settings/general.blade.php`

#### Component Requirements

| Property | Type | Purpose |
|----------|------|---------|
| `storeName` | `string` | Store display name |
| `storeHandle` | `string` | Store handle (read-only or with warning) |
| `defaultCurrency` | `string` | Currency code (e.g., "EUR") |
| `defaultLocale` | `string` | Locale code (e.g., "en") |
| `timezone` | `string` | Timezone string |

| Action | Behavior |
|--------|----------|
| `save()` | Validates and saves store settings |

#### Layout

Two-column layout per section: left side has section label and description, right side has form fields.

**Store details section:**
- Left: Heading "Store details" + description "Basic information about your store."
- Right: Store name input, Store handle input (disabled, with description: "The store handle cannot be changed after creation.")

Separator between sections.

**Defaults section:**
- Left: Heading "Defaults" + description "Currency, language, and timezone settings."
- Right:
  - Default currency: Select (EUR/USD/GBP and more)


switch component wire model label
toggle switch


# Search Results

## Docs Dark Mode > Toggle switch
Docs Dark Mode | livewire/flux@2.x | Source: https://fluxui.dev/docs/dark-mode#livewire/flux

            Toggle switch

A simple toggle switch to allow users to control dark mode from something like a settings page.

<flux:switch x-data x-model="$flux.dark" label="Dark mode"  />

---

## Components Switch > flux:switch
Components Switch | livewire/flux@2.x | Source: https://fluxui.dev/components/switch#livewire/flux

flux:switch

<ui-table-scroll-area>

Prop

Description

    wire:model

Binds the switch to a Livewire property. See the wire:model documentation for more information.

    label

Label text displayed above the switch. When provided, wraps the switch in a flux:field component with an adjacent flux:label component. See the field component.

    description

Help text displayed below the switch. When provided alongside label, appears between the label and switch within the flux:field wrapper. See the field component.

    align

Alignment of the switch relative to its label. Options: right|start (default), left|end.

    disabled

Prevents user interaction with the switch.

</ui-table-scroll-area>

<ui-table-scroll-area>

Attribute

Description

    data-flux-switch

Applied to the root element for styling and identification.

    data-checked

Applied when the switch is in the "on" state.

</ui-table-scroll-area>

---

## Components Switch > Reference
Components Switch | livewire/flux@2.x | Source: https://fluxui.dev/components/switch#livewire/flux

    Reference

---

## Docs Dark Mode > Segmented radio
Docs Dark Mode | livewire/flux@2.x | Source: https://fluxui.dev/docs/dark-mode#livewire/flux

            Segmented radio

A simple toggle switch to allow users to control dark mode from something like a settings page.

<flux:radio.group x-data variant="segmented" x-model="$flux.appearance">    <flux:radio value="light" icon="sun">Light</flux:radio>    <flux:radio value="dark" icon="moon">Dark</flux:radio>    <flux:radio value="system" icon="computer-desktop">System</flux:radio></flux:radio.group>

Alternatively, you can use an icon-only variant to save horizontal space:

<flux:radio.group x-data variant="segmented" x-model="$flux.appearance">    <flux:radio value="light" icon="sun" />    <flux:radio value="dark" icon="moon" />    <flux:radio value="system" icon="computer-desktop" /></flux:radio.group>

---

## Components Switch > Switch
Components Switch | livewire/flux@2.x | Source: https://fluxui.dev/components/switch#livewire/flux

    Switch

Toggle a setting on or off. Suitable for binary options like enabling or disabling features.

        Use switches as auto-saving controls outside forms; checkboxes otherwise.

<flux:field variant="inline">    <flux:label>Enable notifications</flux:label>    <flux:switch wire:model.live="notifications" />    <flux:error name="notifications" /></flux:field>

---

## Components Switch > Fieldset
Components Switch | livewire/flux@2.x | Source: https://fluxui.dev/components/switch#livewire/flux

Fieldset

Group related switches within a fieldset.

<flux:fieldset>    <flux:legend>Email notifications</flux:legend>
        <flux:switch wire:model.live="communication" label="Communication emails" description="Receive emails about your account activity." />        <flux:separator variant="subtle" />        <flux:switch wire:model.live="marketing" label="Marketing emails" description="Receive emails about new products, features, and more." />        <flux:separator variant="subtle" />        <flux:switch wire:model.live="social" label="Social emails" description="Receive emails for friend requests, follows, and more." />        <flux:separator variant="subtle" />        <flux:switch wire:model.live="security" label="Security emails" description="Receive emails about your account activity and security." />
</flux:fieldset>

---

## Components Switch > Left align
Components Switch | livewire/flux@2.x | Source: https://fluxui.dev/components/switch#livewire/flux

Left align

Left align switches for more compact layouts using the align prop.

<flux:fieldset>    <flux:legend>Email notifications</flux:legend>
        <flux:switch label="Communication emails" align="left" />        <flux:switch label="Marketing emails" align="left" />        <flux:switch label="Social emails" align="left" />        <flux:switch label="Security emails" align="left" />
</flux:fieldset>

---

## Layouts Sidebar > flux:sidebar.toggle
Layouts Sidebar | livewire/flux@2.x | Source: https://fluxui.dev/layouts/sidebar#livewire/flux

flux:sidebar.toggle

<ui-table-scroll-area>

Prop

Description

    icon

The icon to display in the toggle button (e.g., bars-2, x-mark).

    inset

Positioning of the toggle button (e.g., left).

</ui-table-scroll-area>

<ui-table-scroll-area>

CSS

Description

    class

Additional CSS classes applied to the toggle button. Common uses: lg:hidden to show only on mobile.

</ui-table-scroll-area>

---

## Docs Dark Mode > Toggle button
Docs Dark Mode | livewire/flux@2.x | Source: https://fluxui.dev/docs/dark-mode#livewire/flux

            Toggle button

A simple toggle button to allow uesrs to control dark mode from something like a navbar or sidebar.

<flux:button x-data x-on:click="$flux.dark = ! $flux.dark" icon="moon" variant="subtle" aria-label="Toggle dark mode" />

---

## Layouts Header > flux:sidebar.toggle
Layouts Header | livewire/flux@2.x | Source: https://fluxui.dev/layouts/header#livewire/flux

flux:sidebar.toggle

<ui-table-scroll-area>

Attribute

Description

    icon

The icon to display in the toggle button (e.g., bars-2, x-mark).

    inset

Positioning of the toggle button (e.g., left).

</ui-table-scroll-area>

<ui-table-scroll-area>

CSS

Description

    class

Additional CSS classes applied to the toggle button. Common uses: lg:hidden to show only on mobile.

</ui-table-scroll-area>

---

## Components Navbar > Profile switcher
Components Navbar | livewire/flux@2.x | Source: https://fluxui.dev/components/navbar#livewire/flux

Profile switcher


Output:
Wall time: 2.2865 seconds
Output:
[{"type":"text","text":"# Search Results\n\n## Docs Dark Mode > Toggle switch\nDocs Dark Mode | livewire/flux@2.x | Source: https://fluxui.dev/docs/dark-mode#livewire/flux\n\n
\n            Toggle switch\n

A simple toggle switch to allow users to control dark mode from something like a settings page.

<flux:switch x-data x-model=\"$flux.dark\" label=\"Dark mode\"  />

\n\n---\n\n## Components Switch > flux:switch\nComponents Switch | livewire/flux@2.x | Source: https://fluxui.dev/components/switch#livewire/flux\n\n
flux:switch

<ui-table-scroll-area>

Prop

Description

\n    wire:model\n

Binds the switch to a Livewire property. See the wire:model documentation for more information.

\n    label\n

Label text displayed above the switch. When provided, wraps the switch in a flux:field component with an adjacent flux:label component. See the field component.

\n    description\n

Help text displayed below the switch. When provided alongside label, appears between the label and switch within the flux:field wrapper. See the field component.

\n    align\n

Alignment of the switch relative to its label. Options: right|start (default), left|end.

\n    disabled\n

Prevents user interaction with the switch.

</ui-table-scroll-area>

<ui-table-scroll-area>

Attribute

Description

\n    data-flux-switch\n

Applied to the root element for styling and identification.

\n    data-checked\n

Applied when the switch is in the \"on\" state.

</ui-table-scroll-area>

\n\n---\n\n## Components Switch > Reference\nComponents Switch | livewire/flux@2.x | Source: https://fluxui.dev/components/switch#livewire/flux\n\n
\n    Reference\n

\n\n---\n\n## Docs Dark Mode > Segmented radio\nDocs Dark Mode | livewire/flux@2.x | Source: https://fluxui.dev/docs/dark-mode#livewire/flux\n\n
\n            Segmented radio\n

A simple toggle switch to allow users to control dark mode from something like a settings page.

<flux:radio.group x-data variant=\"segmented\" x-model=\"$flux.appearance\">    <flux:radio value=\"light\" icon=\"sun\">Light</flux:radio>    <flux:radio value=\"dark\" icon=\"moon\">Dark</flux:radio>    <flux:radio value=\"system\" icon=\"computer-desktop\">System</flux:radio></flux:radio.group>

Alternatively, you can use an icon-only variant to save horizontal space:

<flux:radio.group x-data variant=\"segmented\" x-model=\"$flux.appearance\">    <flux:radio value=\"light\" icon=\"sun\" />    <flux:radio value=\"dark\" icon=\"moon\" />    <flux:radio value=\"system\" icon=\"computer-desktop\" /></flux:radio.group>

\n\n---\n\n## Components Switch > Switch\nComponents Switch | livewire/flux@2.x | Source: https://fluxui.dev/components/switch#livewire/flux\n\n

\n    Switch\n

Toggle a setting on or off. Suitable for binary options like enabling or disabling features.

\n        Use switches as auto-saving controls outside forms; checkboxes otherwise.\n

<flux:field variant=\"inline\">    <flux:label>Enable notifications</flux:label>    <flux:switch wire:model.live=\"notifications\" />    <flux:error name=\"notifications\" /></flux:field>

\n\n---\n\n## Components Switch > Fieldset\nComponents Switch | livewire/flux@2.x | Source: https://fluxui.dev/components/switch#livewire/flux\n\n
Fieldset

Group related switches within a fieldset.

<flux:fieldset>    <flux:legend>Email notifications</flux:legend>
        <flux:switch wire:model.live=\"communication\" label=\"Communication emails\" description=\"Receive emails about your account activity.\" />        <flux:separator variant=\"subtle\" />        <flux:switch wire:model.live=\"marketing\" label=\"Marketing emails\" description=\"Receive emails about new products, features, and more.\" />        <flux:separator variant=\"subtle\" />        <flux:switch wire:model.live=\"social\" label=\"Social emails\" description=\"Receive emails for friend requests, follows, and more.\" />        <flux:separator variant=\"subtle\" />        <flux:switch wire:model.live=\"security\" label=\"Security emails\" description=\"Receive emails about your account activity and security.\" />
</flux:fieldset>

\n\n---\n\n## Components Switch > Left align\nComponents Switch | livewire/flux@2.x | Source: https://fluxui.dev/components/switch#livewire/flux\n\n
Left align

Left align switches for more compact layouts using the align prop.

<flux:fieldset>    <flux:legend>Email notifications</flux:legend>
        <flux:switch label=\"Communication emails\" align=\"left\" />        <flux:switch label=\"Marketing emails\" align=\"left\" />        <flux:switch label=\"Social emails\" align=\"left\" />        <flux:switch label=\"Security emails\" align=\"left\" />
</flux:fieldset>

\n\n---\n\n## Layouts Sidebar > flux:sidebar.toggle\nLayouts Sidebar | livewire/flux@2.x | Source: https://fluxui.dev/layouts/sidebar#livewire/flux\n\n
flux:sidebar.toggle

<ui-table-scroll-area>

Prop

Description

\n    icon\n

The icon to display in the toggle button (e.g., bars-2, x-mark).

\n    inset\n

Positioning of the toggle button (e.g., left).

</ui-table-scroll-area>

<ui-table-scroll-area>

CSS

Description

\n    class\n

Additional CSS classes applied to the toggle button. Common uses: lg:hidden to show only on mobile.

</ui-table-scroll-area>

\n\n---\n\n## Docs Dark Mode > Toggle button\nDocs Dark Mode | livewire/flux@2.x | Source: https://fluxui.dev/docs/dark-mode#livewire/flux\n\n
\n            Toggle button\n

A simple toggle button to allow uesrs to control dark mode from something like a navbar or sidebar.

<flux:button x-data x-on:click=\"$flux.dark = ! $flux.dark\" icon=\"moon\" variant=\"subtle\" aria-label=\"Toggle dark mode\" />

\n\n---\n\n## Layouts Header > flux:sidebar.toggle\nLayouts Header | livewire/flux@2.x | Source: https://fluxui.dev/layouts/header#livewire/flux\n\n
flux:sidebar.toggle

<ui-table-scroll-area>

Attribute

Description

\n    icon\n

The icon to display in the toggle button (e.g., bars-2, x-mark).

\n    inset\n

Positioning of the toggle button (e.g., left).

</ui-table-scroll-area>

<ui-table-scroll-area>

CSS

Description

\n    class\n

Additional CSS classes applied to the toggle button. Common uses: lg:hidden to show only on mobile.

</ui-table-scroll-area>

\n\n---\n\n## Components Navbar > Profile switcher\nComponents Navbar | livewire/flux@2.x | Source: https://fluxui.dev/components/navbar#livewire/flux\n\n
Profile switcher

"}]


I noticed two small spec-alignment details in the discount form: the status control is specified as a switch, and the minimum/end-date fields should explain that blank means no limit. I’m tightening those Blade controls before the QA results come back.


Input:
*** Begin Patch
*** Update File: resources/views/livewire/admin/discounts/form.blade.php
@@

-                    <flux:input wire:model="minimumPurchaseAmount" type="number" step="0.01" min="0" label="Minimum purchase amount" placeholder="0.00" />
+                    <flux:input wire:model="minimumPurchaseAmount" type="number" step="0.01" min="0" label="Minimum purchase amount" description="Leave empty for no minimum" placeholder="0.00" />
                     <flux:error name="minimumPurchaseAmount" />

@@

                 <flux:input wire:model="startsAt" type="datetime-local" label="Start date" />
-                <flux:input wire:model="endsAt" type="datetime-local" label="End date" />
+                <flux:input wire:model="endsAt" type="datetime-local" label="End date" description="Leave empty for no end date" />

@@

-            <flux:checkbox wire:model="isActive" label="{{ $isActive ? 'Active' : 'Disabled' }}" />
+            <flux:switch wire:model="isActive" label="{{ $isActive ? 'Active' : 'Disabled' }}" align="left" />

*** End Patch


Success. Updated the following files:
M resources/views/livewire/admin/discounts/form.blade.php

@@ -64,3 +64,3 @@

-                    <flux:input wire:model="minimumPurchaseAmount" type="number" step="0.01" min="0" label="Minimum purchase amount" placeholder="0.00" />
+                    <flux:input wire:model="minimumPurchaseAmount" type="number" step="0.01" min="0" label="Minimum purchase amount" description="Leave empty for no minimum" placeholder="0.00" />
                     <flux:error name="minimumPurchaseAmount" />
@@ -135,3 +135,3 @@
                 <flux:input wire:model="startsAt" type="datetime-local" label="Start date" />
-                <flux:input wire:model="endsAt" type="datetime-local" label="End date" />
+                <flux:input wire:model="endsAt" type="datetime-local" label="End date" description="Leave empty for no end date" />

@@ -144,3 +144,3 @@

-            <flux:checkbox wire:model="isActive" label="{{ $isActive ? 'Active' : 'Disabled' }}" />
+            <flux:switch wire:model="isActive" label="{{ $isActive ? 'Active' : 'Disabled' }}" align="left" />


Output: {"output":"Success. Updated the following files:\nM resources/views/livewire/admin/discounts/form.blade.php\n","metadata":{"exit_code":0,"duration_seconds":0.0}}


php artisan test --compact tests/Feature/Admin/DiscountManagementTest.php


> build
> vite build

vite v7.3.1 building client environment for production...
transforming...
✓ 2 modules transformed.
Generated an empty chunk: "app".
rendering chunks...
computing gzip size...
public/build/manifest.json              0.33 kB │ gzip:  0.17 kB
public/build/assets/app-BzUBDeUY.css  226.27 kB │ gzip: 31.05 kB
public/build/assets/app-l0sNRNKZ.js     0.00 kB │ gzip:  0.02 kB
✓ built in 411ms


Output:
Chunk ID: bbf250
Wall time: 1.0002 seconds
Process running with session ID 48038
Original token count: 0
Output:


Output:
Chunk ID: 24d997
Wall time: 0.7553 seconds
Process exited with code 0
Original token count: 105
Output:

> build
> vite build

vite v7.3.1 building client environment for production...
transforming...
✓ 2 modules transformed.
Generated an empty chunk: "app".
rendering chunks...
computing gzip size...
public/build/manifest.json              0.33 kB │ gzip:  0.17 kB
public/build/assets/app-BzUBDeUY.css  226.27 kB │ gzip: 31.05 kB
public/build/assets/app-l0sNRNKZ.js     0.00 kB │ gzip:  0.02 kB
✓ built in 411ms


php artisan test --compact tests/Feature/Admin/DiscountManagementTest.php

  .....

  Tests:    5 passed (33 assertions)
  Duration: 2.03s


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

  .....

  Tests:    5 passed (33 assertions)
  Duration: 2.03s


The spec-alignment tweak passes focused tests and the Vite build. I’m opening the form once more to make sure the Flux switch renders without browser errors.


### Ran Playwright code
```js
await page.goto('http://shop.test/admin/discounts/create');
```
### Open tabs
- 0: [Checkout](http://shop.test/checkout)
- 1: (current) [Create discount](http://shop.test/admin/discounts/create)
### Page
- Page URL: http://shop.test/admin/discounts/create
- Page Title: Create discount
### Snapshot
- [Snapshot](.playwright-mcp/page-2026-05-04T02-09-53-963Z.yml)
### Events
- New console entries: .playwright-mcp/console-2026-05-04T02-09-53-767Z.log#L1


Output:
Wall time: 0.2012 seconds
Output:
[{"type":"text","text":"### Ran Playwright code\n```js\nawait page.goto('http://shop.test/admin/discounts/create');\n```\n### Open tabs\n- 0: [Checkout](http://shop.test/checkout)\n- 1: (current) [Create discount](http://shop.test/admin/discounts/create)\n### Page\n- Page URL: http://shop.test/admin/discounts/create\n- Page Title: Create discount\n### Snapshot\n- [Snapshot](.playwright-mcp/page-2026-05-04T02-09-53-963Z.yml)\n### Events\n- New console entries: .playwright-mcp/console-2026-05-04T02-09-53-767Z.log#L1"}]


### Open tabs
- 0: [Checkout](http://shop.test/checkout)
- 1: (current) [Create discount](http://shop.test/admin/discounts/create)
### Page
- Page URL: http://shop.test/admin/discounts/create
- Page Title: Create discount
### Snapshot
```yaml
- generic [active] [ref=e1]:
  - generic [ref=e2]:
    - link "Shop" [ref=e4] [cursor=pointer]:
      - /url: http://shop.test/admin
      - img [ref=e6]
      - generic [ref=e8]: Shop
    - navigation [ref=e9]:
      - generic [ref=e10]:
        - generic [ref=e12]: Platform
        - link "Dashboard" [ref=e15] [cursor=pointer]:
          - /url: http://shop.test/admin
          - img [ref=e17]
          - generic [ref=e19]: Dashboard
      - generic [ref=e20]:
        - generic [ref=e22]: Products
        - generic [ref=e23]:
          - link "Products" [ref=e25] [cursor=pointer]:
            - /url: http://shop.test/admin/products
            - img [ref=e27]
            - generic [ref=e29]: Products
          - link "Collections" [ref=e31] [cursor=pointer]:
            - /url: http://shop.test/admin/collections
            - img [ref=e33]
            - generic [ref=e35]: Collections
          - link "Inventory" [ref=e37] [cursor=pointer]:
            - /url: http://shop.test/admin/inventory
            - img [ref=e39]
            - generic [ref=e41]: Inventory
      - generic [ref=e42]:
        - generic [ref=e44]: Operations
        - generic [ref=e45]:
          - link "Orders" [ref=e47] [cursor=pointer]:
            - /url: http://shop.test/admin/orders
            - img [ref=e49]
            - generic [ref=e51]: Orders
          - link "Customers" [ref=e53] [cursor=pointer]:
            - /url: http://shop.test/admin/customers
            - img [ref=e55]
            - generic [ref=e57]: Customers
          - link "Discounts" [ref=e59] [cursor=pointer]:
            - /url: http://shop.test/admin/discounts
            - img [ref=e61]
            - generic [ref=e64]: Discounts
    - navigation [ref=e66]:
      - link "Repository" [ref=e68] [cursor=pointer]:
        - /url: https://github.com/laravel/livewire-starter-kit
        - img [ref=e70]
        - generic [ref=e75]: Repository
      - link "Documentation" [ref=e77] [cursor=pointer]:
        - /url: https://laravel.com/docs/starter-kits#livewire
        - img [ref=e79]
        - generic [ref=e81]: Documentation
    - button "AA Acme Admin" [ref=e83]:
      - generic [ref=e86]: AA
      - generic [ref=e87]: Acme Admin
      - img [ref=e89]
  - generic [ref=e93]:
    - generic [ref=e94]:
      - generic [ref=e95]:
        - generic [ref=e96]: Create discount
        - paragraph [ref=e97]: Configure the promotion type, value, eligibility, limits, and dates.
      - link "Discounts" [ref=e98] [cursor=pointer]:
        - /url: http://shop.test/admin/discounts
        - img [ref=e99]
        - generic [ref=e101]: Discounts
    - generic [ref=e102]:
      - generic [ref=e104]:
        - generic [ref=e105]: Type
        - radiogroup "Type" [ref=e106]:
          - generic [ref=e107]:
            - radio "Discount code" [checked] [ref=e108]
            - generic [ref=e111]: Discount code
            - generic [ref=e112]: Customers enter a code at checkout
          - generic [ref=e113]:
            - radio "Automatic discount" [ref=e114]
            - generic [ref=e116]: Automatic discount
            - generic [ref=e117]: Applied automatically at checkout
      - generic [ref=e118]:
        - generic [ref=e119]: Code
        - generic [ref=e120]:
          - generic [ref=e122]:
            - generic [ref=e123]: Discount code
            - textbox "Discount code" [ref=e125]:
              - /placeholder: SUMMER20
          - button "Generate" [ref=e127]:
            - img [ref=e129]
            - generic [ref=e132]: Generate
      - generic [ref=e133]:
        - generic [ref=e134]: Value
        - generic [ref=e135]:
          - generic [ref=e136]:
            - generic [ref=e137]: Value type
            - radiogroup "Value type" [ref=e138]:
              - generic [ref=e139]
              - generic [ref=e144]
              - generic [ref=e148]
          - generic [ref=e153]:
            - generic [ref=e154]: Percentage
            - spinbutton "Percentage" [ref=e156]: "10"
      - generic [ref=e157]:
        - generic [ref=e158]: Conditions
        - generic [ref=e159]:
          - generic [ref=e161]:
            - generic [ref=e162]: Minimum purchase amount
            - generic [ref=e163]: Leave empty for no minimum
            - spinbutton "Minimum purchase amount" [ref=e165]
          - generic [ref=e166]:
            - generic [ref=e168]:
              - generic [ref=e169]: Specific products
              - generic [ref=e170]
            - generic [ref=e174]:
              - generic [ref=e175]: Specific collections
              - generic [ref=e176]
      - generic [ref=e179]:
        - generic [ref=e180]: Usage limits
        - generic [ref=e181]:
          - generic [ref=e182]:
            - generic [ref=e183]: Total usage limit
            - spinbutton "Total usage limit" [ref=e185]
          - generic [ref=e187]:
            - checkbox "Limit to one use per customer" [ref=e188]
            - generic [ref=e190]: Limit to one use per customer
      - generic [ref=e191]:
        - generic [ref=e192]: Active dates
        - generic [ref=e193]:
          - generic [ref=e194]:
            - generic [ref=e195]: Start date
            - textbox "Start date" [ref=e197]: 2026-05-04T02:09
          - generic [ref=e198]:
            - generic [ref=e199]: End date
            - generic [ref=e200]: Leave empty for no end date
            - textbox "End date" [ref=e202]
      - generic [ref=e204]:
        - switch "Active" [checked] [ref=e205]
        - generic [ref=e207]: Active
      - generic [ref=e209]:
        - link "Discard" [ref=e210] [cursor=pointer]:
          - /url: http://shop.test/admin/discounts
        - button "Save" [ref=e211]:
          - img [ref=e213]
          - generic [ref=e216]: Save
```


Output:
Wall time: 0.0095 seconds
Output:
[{"type":"text","text":"### Open tabs\n- 0: [Checkout](http://shop.test/checkout)\n- 1: (current) [Create discount](http://shop.test/admin/discounts/create)\n### Page\n- Page URL: http://shop.test/admin/discounts/create\n- Page Title: Create discount\n### Snapshot\n```yaml\n- generic [active] [ref=e1]:\n  - generic [ref=e2]:\n    - link \"Shop\" [ref=e4] [cursor=pointer]:\n      - /url: http://shop.test/admin\n      - img [ref=e6]\n      - generic [ref=e8]: Shop\n    - navigation [ref=e9]:\n      - generic [ref=e10]:\n        - generic [ref=e12]: Platform\n        - link \"Dashboard\" [ref=e15] [cursor=pointer]:\n          - /url: http://shop.test/admin\n          - img [ref=e17]\n          - generic [ref=e19]: Dashboard\n      - generic [ref=e20]:\n        - generic [ref=e22]: Products\n        - generic [ref=e23]:\n          - link \"Products\" [ref=e25] [cursor=pointer]:\n            - /url: http://shop.test/admin/products\n            - img [ref=e27]\n            - generic [ref=e29]: Products\n          - link \"Collections\" [ref=e31] [cursor=pointer]:\n            - /url: http://shop.test/admin/collections\n            - img [ref=e33]\n            - generic [ref=e35]: Collections\n          - link \"Inventory\" [ref=e37] [cursor=pointer]:\n            - /url: http://shop.test/admin/inventory\n            - img [ref=e39]\n            - generic [ref=e41]: Inventory\n      - generic [ref=e42]:\n        - generic [ref=e44]: Operations\n        - generic [ref=e45]:\n          - link \"Orders\" [ref=e47] [cursor=pointer]:\n            - /url: http://shop.test/admin/orders\n            - img [ref=e49]\n            - generic [ref=e51]: Orders\n          - link \"Customers\" [ref=e53] [cursor=pointer]:\n            - /url: http://shop.test/admin/customers\n            - img [ref=e55]\n            - generic [ref=e57]: Customers\n          - link \"Discounts\" [ref=e59] [cursor=pointer]:\n            - /url: http://shop.test/admin/discounts\n            - img [ref=e61]\n            - generic [ref=e64]: Discounts\n    - navigation [ref=e66]:\n      - link \"Repository\" [ref=e68] [cursor=pointer]:\n        - /url: https://github.com/laravel/livewire-starter-kit\n        - img [ref=e70]\n        - generic [ref=e75]: Repository\n      - link \"Documentation\" [ref=e77] [cursor=pointer]:\n        - /url: https://laravel.com/docs/starter-kits#livewire\n        - img [ref=e79]\n        - generic [ref=e81]: Documentation\n    - button \"AA Acme Admin\" [ref=e83]:\n      - generic [ref=e86]: AA\n      - generic [ref=e87]: Acme Admin\n      - img [ref=e89]\n  - generic [ref=e93]:\n    - generic [ref=e94]:\n      - generic [ref=e95]:\n        - generic [ref=e96]: Create discount\n        - paragraph [ref=e97]: Configure the promotion type, value, eligibility, limits, and dates.\n      - link \"Discounts\" [ref=e98] [cursor=pointer]:\n        - /url: http://shop.test/admin/discounts\n        - img [ref=e99]\n        - generic [ref=e101]: Discounts\n    - generic [ref=e102]:\n      - generic [ref=e104]:\n        - generic [ref=e105]: Type\n        - radiogroup \"Type\" [ref=e106]:\n          - generic [ref=e107]:\n            - radio \"Discount code\" [checked] [ref=e108]\n            - generic [ref=e111]: Discount code\n            - generic [ref=e112]: Customers enter a code at checkout\n          - generic [ref=e113]:\n            - radio \"Automatic discount\" [ref=e114]\n            - generic [ref=e116]: Automatic discount\n            - generic [ref=e117]: Applied automatically at checkout\n      - generic [ref=e118]:\n        - generic [ref=e119]: Code\n        - generic [ref=e120]:\n          - generic [ref=e122]:\n            - generic [ref=e123]: Discount code\n            - textbox \"Discount code\" [ref=e125]:\n              - /placeholder: SUMMER20\n          - button \"Generate\" [ref=e127]:\n            - img [ref=e129]\n            - generic [ref=e132]: Generate\n      - generic [ref=e133]:\n        - generic [ref=e134]: Value\n        - generic [ref=e135]:\n          - generic [ref=e136]:\n            - generic [ref=e137]: Value type\n            - radiogroup \"Value type\" [ref=e138]:\n              - generic [ref=e139]\n              - generic [ref=e144]\n              - generic [ref=e148]\n          - generic [ref=e153]:\n            - generic [ref=e154]: Percentage\n            - spinbutton \"Percentage\" [ref=e156]: \"10\"\n      - generic [ref=e157]:\n        - generic [ref=e158]: Conditions\n        - generic [ref=e159]:\n          - generic [ref=e161]:\n            - generic [ref=e162]: Minimum purchase amount\n            - generic [ref=e163]: Leave empty for no minimum\n            - spinbutton \"Minimum purchase amount\" [ref=e165]\n          - generic [ref=e166]:\n            - generic [ref=e168]:\n              - generic [ref=e169]: Specific products\n              - generic [ref=e170]\n            - generic [ref=e174]:\n              - generic [ref=e175]: Specific collections\n              - generic [ref=e176]\n      - generic [ref=e179]:\n        - generic [ref=e180]: Usage limits\n        - generic [ref=e181]:\n          - generic [ref=e182]:\n            - generic [ref=e183]: Total usage limit\n            - spinbutton \"Total usage limit\" [ref=e185]\n          - generic [ref=e187]:\n            - checkbox \"Limit to one use per customer\" [ref=e188]\n            - generic [ref=e190]: Limit to one use per customer\n      - generic [ref=e191]:\n        - generic [ref=e192]: Active dates\n        - generic [ref=e193]:\n          - generic [ref=e194]:\n            - generic [ref=e195]: Start date\n            - textbox \"Start date\" [ref=e197]: 2026-05-04T02:09\n          - generic [ref=e198]:\n            - generic [ref=e199]: End date\n            - generic [ref=e200]: Leave empty for no end date\n            - textbox \"End date\" [ref=e202]\n      - generic [ref=e204]:\n        - switch \"Active\" [checked] [ref=e205]\n        - generic [ref=e207]: Active\n      - generic [ref=e209]:\n        - link \"Discard\" [ref=e210] [cursor=pointer]:\n          - /url: http://shop.test/admin/discounts\n        - button \"Save\" [ref=e211]:\n          - img [ref=e213]\n          - generic [ref=e216]: Save\n```"}]


### Result
Total messages: 3 (Errors: 0, Warnings: 0)
Returning 0 messages for level "error"


Output:
Wall time: 0.0178 seconds
Output:
[{"type":"text","text":"### Result\nTotal messages: 3 (Errors: 0, Warnings: 0)\nReturning 0 messages for level \"error\"\n"}]


The updated form renders the switch and helper descriptions cleanly, with no current browser errors. I’m waiting for the independent QA results now; after that I’ll run the final full suite once more and commit.


The QA agents are still running. I’m giving them a bit more time because this slice touches both specs and Livewire request middleware.


1. **High**: Discount create/update bypasses `DiscountPolicy`. The admin route group only requires a store role, including `support`, while `Form::save()` never calls `authorize()` and the routes do not use `can:create` / `can:update`. A support user can create or edit discounts despite policy rules.
   References: [routes/web.php](/Users/fabianwesner/Herd/shop/routes/web.php:57), [routes/web.php](/Users/fabianwesner/Herd/shop/routes/web.php:67), [Form.php](/Users/fabianwesner/Herd/shop/app/Livewire/Admin/Discounts/Form.php:78), [DiscountPolicy.php](/Users/fabianwesner/Herd/shop/app/Policies/DiscountPolicy.php:22). Add policy enforcement and a support-user forbidden test.

2. **High**: Percent discounts accept values over 100 and decimals, then round to an integer. A `150` percent discount can be saved and later produce a discount greater than the subtotal.
   References: [Form.php](/Users/fabianwesner/Herd/shop/app/Livewire/Admin/Discounts/Form.php:95), [Form.php](/Users/fabianwesner/Herd/shop/app/Livewire/Admin/Discounts/Form.php:284). Add percent-specific validation such as integer/min/max and tests for `101` and decimal input.

3. **Medium**: Code uniqueness validation does not match the saved/lookup semantics. Validation checks the raw `code`, but saving uppercases and trims it, and checkout lookup is case-insensitive. Inputs like `save20` or ` SAVE20 ` can pass validation when `SAVE20` exists, then hit the DB unique index or create inconsistent behavior.
   References: [Form.php](/Users/fabianwesner/Herd/shop/app/Livewire/Admin/Discounts/Form.php:86), [Form.php](/Users/fabianwesner/Herd/shop/app/Livewire/Admin/Discounts/Form.php:259), [DiscountService.php](/Users/fabianwesner/Herd/shop/app/Services/DiscountService.php:22), [DiscountManagementTest.php](/Users/fabianwesner/Herd/shop/tests/Feature/Admin/DiscountManagementTest.php:110). Normalize before validation or use a case-insensitive unique check, then add the missing uniqueness test.

4. **Medium**: Livewire persistent middleware preserves the custom store middlewares but not the `verified` middleware required by the admin routes. If a user becomes unverified after loading the page, later Livewire actions can still proceed.
   References: [routes/web.php](/Users/fabianwesner/Herd/shop/routes/web.php:57), [AppServiceProvider.php](/Users/fabianwesner/Herd/shop/app/Providers/AppServiceProvider.php:124), [DiscountManagementTest.php](/Users/fabianwesner/Herd/shop/tests/Feature/Admin/DiscountManagementTest.php:39). Add `EnsureEmailIsVerified` to persistent middleware and cover an unverified Livewire action request.


Output: {"status":{"019df0be-4901-7860-b0a7-7b1ba065d58c":{"completed":"1. **High**: Discount create/update bypasses `DiscountPolicy`. The admin route group only requires a store role, including `support`, while `Form::save()` never calls `authorize()` and the routes do not use `can:create` / `can:update`. A support user can create or edit discounts despite policy rules.  \n   References: [routes/web.php](/Users/fabianwesner/Herd/shop/routes/web.php:57), [routes/web.php](/Users/fabianwesner/Herd/shop/routes/web.php:67), [Form.php](/Users/fabianwesner/Herd/shop/app/Livewire/Admin/Discounts/Form.php:78), [DiscountPolicy.php](/Users/fabianwesner/Herd/shop/app/Policies/DiscountPolicy.php:22). Add policy enforcement and a support-user forbidden test.\n\n2. **High**: Percent discounts accept values over 100 and decimals, then round to an integer. A `150` percent discount can be saved and later produce a discount greater than the subtotal.  \n   References: [Form.php](/Users/fabianwesner/Herd/shop/app/Livewire/Admin/Discounts/Form.php:95), [Form.php](/Users/fabianwesner/Herd/shop/app/Livewire/Admin/Discounts/Form.php:284). Add percent-specific validation such as integer/min/max and tests for `101` and decimal input.\n\n3. **Medium**: Code uniqueness validation does not match the saved/lookup semantics. Validation checks the raw `code`, but saving uppercases and trims it, and checkout lookup is case-insensitive. Inputs like `save20` or ` SAVE20 ` can pass validation when `SAVE20` exists, then hit the DB unique index or create inconsistent behavior.  \n   References: [Form.php](/Users/fabianwesner/Herd/shop/app/Livewire/Admin/Discounts/Form.php:86), [Form.php](/Users/fabianwesner/Herd/shop/app/Livewire/Admin/Discounts/Form.php:259), [DiscountService.php](/Users/fabianwesner/Herd/shop/app/Services/DiscountService.php:22), [DiscountManagementTest.php](/Users/fabianwesner/Herd/shop/tests/Feature/Admin/DiscountManagementTest.php:110). Normalize before validation or use a case-insensitive unique check, then add the missing uniqueness test.\n\n4. **Medium**: Livewire persistent middleware preserves the custom store middlewares but not the `verified` middleware required by the admin routes. If a user becomes unverified after loading the page, later Livewire actions can still proceed.  \n   References: [routes/web.php](/Users/fabianwesner/Herd/shop/routes/web.php:57), [AppServiceProvider.php](/Users/fabianwesner/Herd/shop/app/Providers/AppServiceProvider.php:124), [DiscountManagementTest.php](/Users/fabianwesner/Herd/shop/tests/Feature/Admin/DiscountManagementTest.php:39). Add `EnsureEmailIsVerified` to persistent middleware and cover an unverified Livewire action request."}},"timed_out":false}