Error: File content (26132 tokens) exceeds maximum allowed tokens (25000). Please use offset and limit parameters to read specific portions of the file, or use the GrepTool to search for specific content.


     1→# 08 - Playwright E2E Test Plan
     2→
     3→> Complete end-to-end browser test specification for the e-commerce platform using Pest v4 browser testing (Playwright-backed). All tests verify the entire application works through a real browser. This document defines requirements and specifications for the coding agent to implement.
     4→
     5→---
     6→
     7→## Test Suite Summary
     8→
     9→| Suite | File | Tests | Purpose | Dependencies |
    10→|-------|------|:-----:|---------|--------------|
    11→| 1 - Smoke Tests | `tests/Browser/SmokeTest.php` | 10 | Hit every major page, assert HTTP 200 and no JS errors | Store 1, Products, Collections, Pages |
    12→| 2 - Admin Authentication | `tests/Browser/Admin/AuthenticationTest.php` | 10 | Admin login, logout, invalid credentials, session access control | Admin user |
    13→| 3 - Admin Product Management | `tests/Browser/Admin/ProductManagementTest.php` | 7 | Product CRUD: listing, creating, editing, archiving, filtering | Admin user, Products (incl. #15 draft) |
    14→| 4 - Admin Order Management | `tests/Browser/Admin/OrderManagementTest.php` | 11 | Order listing, filtering, detail view, fulfillment, refund, confirm payment, fulfillment guard, mark shipped/delivered | Admin user, Orders (#1001, #1005), Customer |
    15→| 5 - Admin Discount Management | `tests/Browser/Admin/DiscountManagementTest.php` | 6 | Discount code listing, creation, editing | Admin user, Discounts |
    16→| 6 - Admin Settings | `tests/Browser/Admin/SettingsTest.php` | 7 | Store settings, shipping zones, tax config, domains | Admin user, Shipping zones, Store 1 |
    17→| 7 - Storefront Browsing | `tests/Browser/Storefront/BrowsingTest.php` | 15 | Home page, collections, product detail, variants, search, pages | Products, Collections, Pages, Products #15/#17/#18 |
    18→| 8 - Cart Flow | `tests/Browser/Storefront/CartTest.php` | 12 | Cart lifecycle: add, update qty, remove, discounts, totals | Products, Discounts (all codes) |
    19→| 9 - Checkout Flow | `tests/Browser/Storefront/CheckoutTest.php` | 13 | Full checkout: contact, address, shipping, payment method selection, payment, confirmation, magic card decline, bank transfer flow | Products, Shipping zones, Discounts |
    20→| 10 - Customer Account | `tests/Browser/Storefront/CustomerAccountTest.php` | 12 | Registration, login, order history, addresses, logout | Customer user, Orders (#1001/#1002/#1004) |
    21→| 11 - Inventory Enforcement | `tests/Browser/Storefront/InventoryTest.php` | 4 | Inventory policy enforcement: deny vs. continue | Products #17 (deny), #18 (continue), #1 |
    22→| 12 - Tenant Isolation | `tests/Browser/Storefront/TenantIsolationTest.php` | 5 | Multi-store data isolation verification | Store 1, Admin user, Customer user |
    23→| 13 - Responsive / Mobile | `tests/Browser/Storefront/ResponsiveTest.php` | 8 | Mobile and tablet viewport rendering and interaction | Products, Admin user |
    24→| 14 - Accessibility | `tests/Browser/Storefront/AccessibilityTest.php` | 11 | Heading hierarchy, ARIA labels, form labels, keyboard nav | Products, Store 1 |
    25→| 15 - Admin Collections | `tests/Browser/Admin/CollectionManagementTest.php` | 3 | Collection CRUD in admin | Admin user, Collections |
    26→| 16 - Admin Customers | `tests/Browser/Admin/CustomerManagementTest.php` | 3 | Customer listing and detail views | Admin user, Customer user, Orders |
    27→| 17 - Admin Pages | `tests/Browser/Admin/PageManagementTest.php` | 3 | CMS page management | Admin user, Pages |
    28→| 18 - Admin Analytics | `tests/Browser/Admin/AnalyticsTest.php` | 3 | Analytics dashboard rendering | Admin user, Orders |
    29→| **Total** | **18 files** | **143** | | |
    30→
    31→---
    32→
    33→## Test Infrastructure Requirements
    34→
    35→### Framework
    36→
    37→- **Test runner:** Pest v4 with browser testing capabilities (Playwright under the hood)
    38→- **Test traits:** Each browser test file must use `TestCase` and `RefreshDatabase`
    39→- **Configuration:** Register the `Browser` test directory in `tests/Pest.php` with appropriate base classes
    40→- **Base URL:** Configured via `APP_URL` in `.env.testing`, set to `http://acme-fashion.test`
    41→
    42→### Pest v4 Browser Testing API Reference
    43→
    44→The coding agent should use these Pest v4 browser methods:
    45→
    46→| Method | Purpose |
    47→|--------|---------|
    48→| `visit(url)` | Open a real browser and navigate to the given URL |
    49→| `visit([url1, url2, ...])` | Visit multiple pages (for batch smoke testing) |
    50→| `fill(field, value)` | Type a value into a named form field |
    51→| `click(text)` | Click a visible element matching the given text |
    52→| `assertSee(text)` | Assert the page contains the given visible text |
    53→| `assertNoJavascriptErrors()` | Assert no uncaught JS exceptions occurred |
    54→| `assertNoConsoleLogs()` | Assert no console warnings or errors were logged |
    55→
    56→Additionally, standard Laravel test features are available within browser tests: factories, `Event::fake()`, `Notification::fake()`, `$this->actingAs()`, and model queries.
    57→
    58→### Environment Configuration
    59→
    60→The `.env.testing` file must contain the following keys:
    61→
    62→| Key | Value | Purpose |
    63→|-----|-------|---------|
    64→| `APP_ENV` | `testing` | Laravel environment |
    65→| `APP_URL` | `http://acme-fashion.test` | Browser base URL (served by Herd) |
    66→| `DB_CONNECTION` | `sqlite` | Database driver |
    67→| `DB_DATABASE` | Absolute path to test SQLite file | Isolated test database |
    68→| `PAYMENT_PROVIDER` | `mock` | Mock PSP - no external API keys needed |
    69→| `MAIL_MAILER` | `array` | Trap emails in memory |
    70→| `QUEUE_CONNECTION` | `sync` | Process jobs synchronously |
    71→
    72→### Prerequisites Before Running
    73→
    74→1. Database seeded with `php artisan migrate:fresh --seed`
    75→2. Laravel Herd serving `acme-fashion.test`
    76→3. Playwright browsers installed via `npx playwright install`
    77→4. Frontend assets built via `npm run build`
    78→
    79→---
    80→
    81→## Seeded Data Dependency Table
    82→
    83→All tests depend on the database seeder providing this baseline data. Each test gets a fresh database via `RefreshDatabase`, then the seeder repopulates it.
    84→
    85→Each browser test file must set `protected bool $seed = true;` on the test class (or call `$this->seed(DatabaseSeeder::class)` in `beforeEach`) to ensure the database is populated after RefreshDatabase clears it. Laravel's `RefreshDatabase` trait does NOT automatically run seeders unless explicitly configured.
    86→
    87→| Entity | Key Details | Used By Suites |
    88→|--------|-------------|----------------|
    89→| **Admin user** | Email: `admin@acme.test`, password: `password`, role: `owner` on Store 1 | 2, 3, 4, 5, 6, 12, 13, 15, 16, 17, 18 |
    90→| **Customer user** | Email: `customer@acme.test`, password: `password`, name: "John Doe", belongs to Store 1 | 10, 12 |
    91→| **Store 1** | Name: "Acme Fashion", handle: `acme-fashion`, domain: `acme-fashion.test`, currency: EUR | 1, 6, 7, 12, 14 |
    92→| **Product #1** | Title: "Classic Cotton T-Shirt", handle: `classic-cotton-t-shirt`, price: 24.99 EUR, status: active, options: Size (S/M/L/XL), Color (Black/White/Navy), inventory: in stock | 1, 3, 7, 8, 9, 11, 13, 14 |
    93→| **Product #2** | Title: "Premium Slim Fit Jeans", handle: `premium-slim-fit-jeans`, status: active, has compare-at pricing (sale), options: Size, Color | 7, 8 |
    94→| **Product #15** | Status: `draft` - must NOT appear on storefront | 3, 7 |
    95→| **Product #17** | Status: active, inventory: 0 on hand, policy: `deny` - displays "Sold out", add-to-cart disabled | 7, 11 |
    96→| **Product #18** | Status: active, inventory: 0 on hand, policy: `continue` - displays "Available on backorder", add-to-cart enabled | 7, 11 |
    97→| **Products (20 total)** | Seeded across collections, with variants, options, images, inventory | 1, 3, 7, 8, 9, 11, 13 |
    98→| **Collections** | "T-Shirts" (handle: `t-shirts`), "New Arrivals" (handle: `new-arrivals`), plus others | 1, 7, 13, 15 |
    99→| **Orders** | #1001 (status: paid, fulfillment: unfulfilled, customer: customer@acme.test), #1002, #1004 also belonging to the same customer | 4, 10, 16, 18 |
   100→| **Discounts** | WELCOME10 (10% off, min 20 EUR purchase, active), FLAT5 (5 EUR fixed, active), FREESHIP (free shipping, active), EXPIRED20 (expired end date), MAXED (usage_count >= usage_limit) | 5, 8, 9 |
   101→| **Shipping zones** | "Domestic" zone (countries: DE) with rate "Standard Shipping" at 4.99 EUR flat; "International" zone with its own rates | 6, 9 |
   102→| **Tax settings** | Store 1: manual mode, prices_include_tax varies by test | 6 |
   103→| **Pages** | "About" (handle: `about`, status: published) | 1, 7, 17 |
   104→| **Navigation** | Main menu with links to collections, about page | 7 |
   105→
   106→---
   107→
   108→## Test Execution Matrix
   109→
   110→Tests should be executed in the order shown below. Later suites depend on the infrastructure and pages verified by earlier suites.
   111→
   112→```mermaid
   113→graph TD
   114→    S1[Suite 1: Smoke Tests] --> S2[Suite 2: Admin Auth]
   115→    S1 --> S7[Suite 7: Storefront Browsing]
   116→    S2 --> S3[Suite 3: Admin Products]
   117→    S2 --> S4[Suite 4: Admin Orders]
   118→    S2 --> S5[Suite 5: Admin Discounts]
   119→    S2 --> S6[Suite 6: Admin Settings]
   120→    S2 --> S15[Suite 15: Admin Collections]
   121→    S2 --> S16[Suite 16: Admin Customers]
   122→    S2 --> S17[Suite 17: Admin Pages]
   123→    S2 --> S18[Suite 18: Admin Analytics]
   124→    S7 --> S8[Suite 8: Cart Flow]
   125→    S8 --> S9[Suite 9: Checkout Flow]
   126→    S7 --> S10[Suite 10: Customer Account]
   127→    S7 --> S11[Suite 11: Inventory Enforcement]
   128→    S7 --> S12[Suite 12: Tenant Isolation]
   129→    S7 --> S13[Suite 13: Responsive / Mobile]
   130→    S7 --> S14[Suite 14: Accessibility]
   131→```
   132→
   133→| Execution Phase | Suites | Rationale |
   134→|-----------------|--------|-----------|
   135→| Phase 1 | Suite 1 (Smoke) | Verifies all routes load before deeper testing |
   136→| Phase 2 | Suites 2, 7 | Core auth and storefront browsing - gate to all further tests |
   137→| Phase 3 | Suites 3, 4, 5, 6, 10, 11, 15, 16, 17, 18 | Admin CRUD and storefront feature suites (can run in parallel) |
   138→| Phase 4 | Suite 8 (Cart) | Depends on storefront browsing working |
   139→| Phase 5 | Suite 9 (Checkout) | Depends on cart flow working. Tests payment method selection (credit card, PayPal, bank transfer), magic card declines, and bank transfer instructions. |
   140→| Phase 6 | Suites 12, 13, 14 | Cross-cutting concerns: isolation, responsive, accessibility |
   141→
   142→---
   143→
   144→## Test Directory Structure
   145→
   146→```
   147→tests/Browser/
   148→    SmokeTest.php
   149→    Admin/
   150→        AuthenticationTest.php
   151→        ProductManagementTest.php
   152→        OrderManagementTest.php
   153→        DiscountManagementTest.php
   154→        SettingsTest.php
   155→        CollectionManagementTest.php
   156→        CustomerManagementTest.php
   157→        PageManagementTest.php
   158→        AnalyticsTest.php
   159→    Storefront/
   160→        BrowsingTest.php
   161→        CartTest.php
   162→        CheckoutTest.php
   163→        CustomerAccountTest.php
   164→        InventoryTest.php
   165→        TenantIsolationTest.php
   166→        ResponsiveTest.php
   167→        AccessibilityTest.php
   168→```
   169→
   170→---
   171→
   172→## Suite 1: Smoke Tests
   173→
   174→**File:** `tests/Browser/SmokeTest.php`
   175→
   176→**Purpose:** Hit every major public and admin page, assert they load without JavaScript errors. This is the fastest way to catch broken routes, missing views, or uncaught JS exceptions.
   177→
   178→---
   179→
   180→### Test 1.1: Loads the storefront home page
   181→
   182→**Preconditions:** Seeded store
   183→
   184→**Steps:**
   185→1. Navigate to `/`
   186→
   187→**Expected Results:**
   188→- Page displays "Acme Fashion"
   189→- No JavaScript errors
   190→
   191→---
   192→
   193→### Test 1.2: Loads a collection page
   194→
   195→**Preconditions:** Seeded collections
   196→
   197→**Steps:**
   198→1. Navigate to `/collections/t-shirts`
   199→
   200→**Expected Results:**

# 08 - Playwright E2E Test Plan

> Complete end-to-end browser test specification for the e-commerce platform using Pest v4 browser testing (Playwright-backed). All tests verify the entire application works through a real browser. This document defines requirements and specifications for the coding agent to implement.

---

## Test Suite Summary

| Suite | File | Tests | Purpose | Dependencies |
|-------|------|:-----:|---------|--------------|
| 1 - Smoke Tests | `tests/Browser/SmokeTest.php` | 10 | Hit every major page, assert HTTP 200 and no JS errors | Store 1, Products, Collections, Pages |
| 2 - Admin Authentication | `tests/Browser/Admin/AuthenticationTest.php` | 10 | Admin login, logout, invalid credentials, session access control | Admin user |
| 3 - Admin Product Management | `tests/Browser/Admin/ProductManagementTest.php` | 7 | Product CRUD: listing, creating, editing, archiving, filtering | Admin user, Products (incl. #15 draft) |
| 4 - Admin Order Management | `tests/Browser/Admin/OrderManagementTest.php` | 11 | Order listing, filtering, detail view, fulfillment, refund, confirm payment, fulfillment guard, mark shipped/delivered | Admin user, Orders (#1001, #1005), Customer |
| 5 - Admin Discount Management | `tests/Browser/Admin/DiscountManagementTest.php` | 6 | Discount code listing, creation, editing | Admin user, Discounts |
| 6 - Admin Settings | `tests/Browser/Admin/SettingsTest.php` | 7 | Store settings, shipping zones, tax config, domains | Admin user, Shipping zones, Store 1 |
| 7 - Storefront Browsing | `tests/Browser/Storefront/BrowsingTest.php` | 15 | Home page, collections, product detail, variants, search, pages | Products, Collections, Pages, Products #15/#17/#18 |
| 8 - Cart Flow | `tests/Browser/Storefront/CartTest.php` | 12 | Cart lifecycle: add, update qty, remove, discounts, totals | Products, Discounts (all codes) |
| 9 - Checkout Flow | `tests/Browser/Storefront/CheckoutTest.php` | 13 | Full checkout: contact, address, shipping, payment method selection, payment, confirmation, magic card decline, bank transfer flow | Products, Shipping zones, Discounts |
| 10 - Customer Account | `tests/Browser/Storefront/CustomerAccountTest.php` | 12 | Registration, login, order history, addresses, logout | Customer user, Orders (#1001/#1002/#1004) |
| 11 - Inventory Enforcement | `tests/Browser/Storefront/InventoryTest.php` | 4 | Inventory policy enforcement: deny vs. continue | Products #17 (deny), #18 (continue), #1 |
| 12 - Tenant Isolation | `tests/Browser/Storefront/TenantIsolationTest.php` | 5 | Multi-store data isolation verification | Store 1, Admin user, Customer user |
| 13 - Responsive / Mobile | `tests/Browser/Storefront/ResponsiveTest.php` | 8 | Mobile and tablet viewport rendering and interaction | Products, Admin user |
| 14 - Accessibility | `tests/Browser/Storefront/AccessibilityTest.php` | 11 | Heading hierarchy, ARIA labels, form labels, keyboard nav | Products, Store 1 |
| 15 - Admin Collections | `tests/Browser/Admin/CollectionManagementTest.php` | 3 | Collection CRUD in admin | Admin user, Collections |
| 16 - Admin Customers | `tests/Browser/Admin/CustomerManagementTest.php` | 3 | Customer listing and detail views | Admin user, Customer user, Orders |
| 17 - Admin Pages | `tests/Browser/Admin/PageManagementTest.php` | 3 | CMS page management | Admin user, Pages |
| 18 - Admin Analytics | `tests/Browser/Admin/AnalyticsTest.php` | 3 | Analytics dashboard rendering | Admin user, Orders |
| **Total** | **18 files** | **143** | | |

---

## Test Infrastructure Requirements

### Framework

- **Test runner:** Pest v4 with browser testing capabilities (Playwright under the hood)
- **Test traits:** Each browser test file must use `TestCase` and `RefreshDatabase`
- **Configuration:** Register the `Browser` test directory in `tests/Pest.php` with appropriate base classes
- **Base URL:** Configured via `APP_URL` in `.env.testing`, set to `http://acme-fashion.test`

### Pest v4 Browser Testing API Reference

The coding agent should use these Pest v4 browser methods:

| Method | Purpose |
|--------|---------|
| `visit(url)` | Open a real browser and navigate to the given URL |
| `visit([url1, url2, ...])` | Visit multiple pages (for batch smoke testing) |
| `fill(field, value)` | Type a value into a named form field |
| `click(text)` | Click a visible element matching the given text |
| `assertSee(text)` | Assert the page contains the given visible text |
| `assertNoJavascriptErrors()` | Assert no uncaught JS exceptions occurred |
| `assertNoConsoleLogs()` | Assert no console warnings or errors were logged |

Additionally, standard Laravel test features are available within browser tests: factories, `Event::fake()`, `Notification::fake()`, `$this->actingAs()`, and model queries.

### Environment Configuration

The `.env.testing` file must contain the following keys:

| Key | Value | Purpose |
|-----|-------|---------|
| `APP_ENV` | `testing` | Laravel environment |
| `APP_URL` | `http://acme-fashion.test` | Browser base URL (served by Herd) |
| `DB_CONNECTION` | `sqlite` | Database driver |
| `DB_DATABASE` | Absolute path to test SQLite file | Isolated test database |
| `PAYMENT_PROVIDER` | `mock` | Mock PSP - no external API keys needed |
| `MAIL_MAILER` | `array` | Trap emails in memory |
| `QUEUE_CONNECTION` | `sync` | Process jobs synchronously |

### Prerequisites Before Running

1. Database seeded with `php artisan migrate:fresh --seed`
2. Laravel Herd serving `acme-fashion.test`
3. Playwright browsers installed via `npx playwright install`
4. Frontend assets built via `npm run build`

---

## Seeded Data Dependency Table

All tests depend on the database seeder providing this baseline data. Each test gets a fresh database via `RefreshDatabase`, then the seeder repopulates it.

Each browser test file must set `protected bool $seed = true;` on the test class (or call `$this->seed(DatabaseSeeder::class)` in `beforeEach`) to ensure the database is populated after RefreshDatabase clears it. Laravel's `RefreshDatabase` trait does NOT automatically run seeders unless explicitly configured.

| Entity | Key Details | Used By Suites |
|--------|-------------|----------------|
| **Admin user** | Email: `admin@acme.test`, password: `password`, role: `owner` on Store 1 | 2, 3, 4, 5, 6, 12, 13, 15, 16, 17, 18 |
| **Customer user** | Email: `customer@acme.test`, password: `password`, name: "John Doe", belongs to Store 1 | 10, 12 |
| **Store 1** | Name: "Acme Fashion", handle: `acme-fashion`, domain: `acme-fashion.test`, currency: EUR | 1, 6, 7, 12, 14 |
| **Product #1** | Title: "Classic Cotton T-Shirt", handle: `classic-cotton-t-shirt`, price: 24.99 EUR, status: active, options: Size (S/M/L/XL), Color (Black/White/Navy), inventory: in stock | 1, 3, 7, 8, 9, 11, 13, 14 |
| **Product #2** | Title: "Premium Slim Fit Jeans", handle: `premium-slim-fit-jeans`, status: active, has compare-at pricing (sale), options: Size, Color | 7, 8 |
| **Product #15** | Status: `draft` - must NOT appear on storefront | 3, 7 |
| **Product #17** | Status: active, inventory: 0 on hand, policy: `deny` - displays "Sold out", add-to-cart disabled | 7, 11 |
| **Product #18** | Status: active, inventory: 0 on hand, policy: `continue` - displays "Available on backorder", add-to-cart enabled | 7, 11 |
| **Products (20 total)** | Seeded across collections, with variants, options, images, inventory | 1, 3, 7, 8, 9, 11, 13 |
| **Collections** | "T-Shirts" (handle: `t-shirts`), "New Arrivals" (handle: `new-arrivals`), plus others | 1, 7, 13, 15 |
| **Orders** | #1001 (status: paid, fulfillment: unfulfilled, customer: customer@acme.test), #1002, #1004 also belonging to the same customer | 4, 10, 16, 18 |
| **Discounts** | WELCOME10 (10% off, min 20 EUR purchase, active), FLAT5 (5 EUR fixed, active), FREESHIP (free shipping, active), EXPIRED20 (expired end date), MAXED (usage_count >= usage_limit) | 5, 8, 9 |
| **Shipping zones** | "Domestic" zone (countries: DE) with rate "Standard Shipping" at 4.99 EUR flat; "International" zone with its own rates | 6, 9 |
| **Tax settings** | Store 1: manual mode, prices_include_tax varies by test | 6 |
| **Pages** | "About" (handle: `about`, status: published) | 1, 7, 17 |
| **Navigation** | Main menu with links to collections, about page | 7 |

---

## Test Execution Matrix

Tests should be executed in the order shown below. Later suites depend on the infrastructure and pages verified by earlier suites.

```mermaid
graph TD
    S1[Suite 1: Smoke Tests] --> S2[Suite 2: Admin Auth]
    S1 --> S7[Suite 7: Storefront Browsing]
    S2 --> S3[Suite 3: Admin Products]
    S2 --> S4[Suite 4: Admin Orders]
    S2 --> S5[Suite 5: Admin Discounts]
    S2 --> S6[Suite 6: Admin Settings]
    S2 --> S15[Suite 15: Admin Collections]
    S2 --> S16[Suite 16: Admin Customers]
    S2 --> S17[Suite 17: Admin Pages]
    S2 --> S18[Suite 18: Admin Analytics]
    S7 --> S8[Suite 8: Cart Flow]
    S8 --> S9[Suite 9: Checkout Flow]
    S7 --> S10[Suite 10: Customer Account]
    S7 --> S11[Suite 11: Inventory Enforcement]
    S7 --> S12[Suite 12: Tenant Isolation]
    S7 --> S13[Suite 13: Responsive / Mobile]
    S7 --> S14[Suite 14: Accessibility]
```

| Execution Phase | Suites | Rationale |
|-----------------|--------|-----------|
| Phase 1 | Suite 1 (Smoke) | Verifies all routes load before deeper testing |
| Phase 2 | Suites 2, 7 | Core auth and storefront browsing - gate to all further tests |
| Phase 3 | Suites 3, 4, 5, 6, 10, 11, 15, 16, 17, 18 | Admin CRUD and storefront feature suites (can run in parallel) |
| Phase 4 | Suite 8 (Cart) | Depends on storefront browsing working |
| Phase 5 | Suite 9 (Checkout) | Depends on cart flow working. Tests payment method selection (credit card, PayPal, bank transfer), magic card declines, and bank transfer instructions. |
| Phase 6 | Suites 12, 13, 14 | Cross-cutting concerns: isolation, responsive, accessibility |

---

## Test Directory Structure

```
tests/Browser/
    SmokeTest.php
    Admin/
        AuthenticationTest.php
        ProductManagementTest.php
        OrderManagementTest.php
        DiscountManagementTest.php
        SettingsTest.php
        CollectionManagementTest.php
        CustomerManagementTest.php
        PageManagementTest.php
        AnalyticsTest.php
    Storefront/
        BrowsingTest.php
        CartTest.php
        CheckoutTest.php
        CustomerAccountTest.php
        InventoryTest.php
        TenantIsolationTest.php
        ResponsiveTest.php
        AccessibilityTest.php
```

---

## Suite 1: Smoke Tests

**File:** `tests/Browser/SmokeTest.php`

**Purpose:** Hit every major public and admin page, assert they load without JavaScript errors. This is the fastest way to catch broken routes, missing views, or uncaught JS exceptions.

---

### Test 1.1: Loads the storefront home page

**Preconditions:** Seeded store

**Steps:**
1. Navigate to `/`

**Expected Results:**
- Page displays "Acme Fashion"
- No JavaScript errors

---

### Test 1.2: Loads a collection page

**Preconditions:** Seeded collections

**Steps:**
1. Navigate to `/collections/t-shirts`

**Expected Results:**


   400→| `layouts/` | `app.blade.php` |
   401→| `pages/` | `home.blade.php`, `collection.blade.php`, `product.blade.php`, `cart.blade.php`, `search.blade.php`, `page.blade.php` |
   402→| `checkout/` | `index.blade.php`, `confirmation.blade.php` |
   403→| `account/` | `login.blade.php`, `register.blade.php`, `dashboard.blade.php` |
   404→| `account/orders/` | `index.blade.php`, `show.blade.php` |
   405→| `account/addresses/` | `index.blade.php` |
   406→| `errors/` | `404.blade.php`, `503.blade.php` |
   407→| `components/` | `product-card.blade.php`, `price.blade.php`, `badge.blade.php`, `quantity-selector.blade.php`, `address-form.blade.php`, `order-summary.blade.php`, `breadcrumbs.blade.php`, `pagination.blade.php` |
   408→
   409→Base layout (`app.blade.php`): header with nav, announcement bar, main content, footer, cart drawer. All with dark mode support via `dark:` prefix.
   410→
   411→**ThemeSettings Service:** Singleton registered in `AppServiceProvider`, loads and caches active theme settings for the current store.
   412→
   413→#### Step 3.4: Storefront Livewire Components
   414→
   415→**Files to create:**
   416→
   417→| Component | Purpose |
   418→|-----------|---------|
   419→| `Storefront\Home` | Dynamic sections based on theme settings |
   420→| `Storefront\Collections\Index` | All collections grid |
   421→| `Storefront\Collections\Show` | Product grid with filters/sort/pagination |
   422→| `Storefront\Products\Show` | Variant selection, image gallery, add-to-cart |
   423→| `Storefront\Cart\Show` | Full cart page with line items and totals |
   424→| `Storefront\CartDrawer` | Slide-out cart drawer (always in layout) |
   425→| `Storefront\Search\Index` | Search results with filters |
   426→| `Storefront\Search\Modal` | Search modal with autocomplete |
   427→| `Storefront\Pages\Show` | Static CMS page rendering |
   428→
   429→#### Step 3.5: Navigation Service
   430→
   431→**File to create:** `App\Services\NavigationService`
   432→
   433→| Method | Description |
   434→|--------|-------------|
   435→| `buildTree(NavigationMenu $menu): array` | Builds navigation trees from navigation_menus / navigation_items |
   436→| `resolveUrl(NavigationItem $item): string` | Resolves URLs for page, collection, product type items |
   437→
   438→Caches per store with 5-minute TTL.
   439→
   440→---
   441→
   442→### Phase 4: Cart, Checkout, Discounts, Shipping, Taxes
   443→
   444→**Priority:** HIGH - core shopping flow.
   445→
   446→#### Step 4.1: Migrations (Batch 4-5)
   447→
   448→1. `create_carts_table` - FK: `store_id`, `customer_id`
   449→2. `create_cart_lines_table` - FK: `cart_id`, `variant_id`
   450→3. `create_checkouts_table` - FK: `store_id`, `cart_id`, `customer_id`
   451→4. `create_shipping_zones_table` - FK: `store_id`
   452→5. `create_shipping_rates_table` - FK: `zone_id`
   453→6. `create_tax_settings_table` - PK: `store_id`
   454→7. `create_discounts_table` - FK: `store_id`
   455→
   456→#### Step 4.2: Models
   457→
   458→**Files to create:**
   459→
   460→| Type | Name | Purpose |
   461→|------|------|---------|
   462→| Model (with migration, factory, seeder) | `Cart` | Shopping cart entity |
   463→| Model (with migration, factory, seeder) | `CartLine` | Individual cart line item |
   464→| Model (with migration, factory, seeder) | `Checkout` | Checkout session entity |
   465→| Model (with migration, factory, seeder) | `ShippingZone` | Shipping zone configuration |
   466→| Model (with migration, factory, seeder) | `ShippingRate` | Shipping rate within a zone |
   467→| Model (with migration, factory, seeder) | `TaxSettings` | Per-store tax configuration |
   468→| Model (with migration, factory, seeder) | `Discount` | Discount code or automatic discount |
   469→
   470→**Relationship definitions:**
   471→
   472→| Model | Key Relationships |
   473→|-------|-------------------|
   474→| `Cart` | `belongsTo(Store)`, `belongsTo(Customer)`, `hasMany(CartLine)`, `hasMany(Checkout)` |
   475→| `CartLine` | `belongsTo(Cart)`, `belongsTo(ProductVariant)` |
   476→| `Checkout` | `belongsTo(Store)`, `belongsTo(Cart)`, `belongsTo(Customer)` |
   477→| `ShippingZone` | `belongsTo(Store)`, `hasMany(ShippingRate)` |
   478→| `ShippingRate` | `belongsTo(ShippingZone)` |
   479→| `TaxSettings` | `belongsTo(Store)` |
   480→| `Discount` | `belongsTo(Store)` |
   481→
   482→**Enums to create:**
   483→
   484→| Enum | Values |
   485→|------|--------|
   486→| `CartStatus` | `Active`, `Converted`, `Abandoned` |
   487→| `CheckoutStatus` | `Started`, `Addressed`, `ShippingSelected`, `PaymentPending`, `Completed`, `Expired` |
   488→| `DiscountType` | `Code`, `Automatic` |
   489→| `DiscountValueType` | `Percent` (`percent`), `Fixed` (`fixed`), `FreeShipping` (`free_shipping`) -- PascalCase cases backed by lowercase string values stored in DB |
   490→| `DiscountStatus` | `Draft`, `Active`, `Expired`, `Disabled` |
   491→| `ShippingRateType` | `Flat`, `Weight`, `Price`, `Carrier` |
   492→| `TaxMode` | `Manual`, `Provider` |
   493→
   494→#### Step 4.3: Cart Service
   495→
   496→**File to create:** `App\Services\CartService`
   497→
   498→| Method | Description |
   499→|--------|-------------|
   500→| `create(Store, ?Customer): Cart` | Creates cart with store currency, version 1 |
   501→| `addLine(Cart, int $variantId, int $qty): CartLine` | Validates variant/product active, inventory, increments existing line or creates new |
   502→| `updateLineQuantity(Cart, int $lineId, int $qty): CartLine` | Validates quantity, recalculates amounts |
   503→| `removeLine(Cart, int $lineId): void` | Deletes line, increments version |
   504→| `getOrCreateForSession(Store, ?Customer): Cart` | Session-based cart binding |
   505→| `mergeOnLogin(Cart $guest, Cart $customer): Cart` | Merges guest cart into customer cart |
   506→
   507→All mutations increment `cart_version`. API clients must send `expected_version` or receive 409 Conflict.
   508→
   509→#### Step 4.4: Discount Service
   510→
   511→**File to create:** `App\Services\DiscountService`
   512→
   513→| Method | Description |
   514→|--------|-------------|
   515→| `validate(string $code, Store $store, Cart $cart): Discount` | Validates code exists, is active, dates valid, usage limit not reached, minimum purchase met |
   516→| `calculate(Discount $discount, int $subtotal, array $lines): DiscountResult` | Calculates amount, allocates proportionally across qualifying lines |
   517→
   518→Case-insensitive code lookup. Throws `InvalidDiscountException` with specific reason codes.
   519→
   520→#### Step 4.5: Shipping Calculator
   521→
   522→**File to create:** `App\Services\ShippingCalculator`
   523→
   524→| Method | Description |
   525→|--------|-------------|
   526→| `getAvailableRates(Store $store, array $address): Collection` | Matches zones by country/region, returns active rates |
   527→| `calculate(ShippingRate $rate, Cart $cart): int` | Calculates cost based on rate type (flat/weight/price) |
   528→
   529→Zone matching: checks `countries_json` for country code, then `regions_json` for region code.
   530→
   531→#### Step 4.6: Tax Calculator
   532→
   533→**File to create:** `App\Services\TaxCalculator`
   534→
   535→| Method | Description |
   536→|--------|-------------|
   537→| `calculate(int $amount, TaxSettings $settings, array $address): TaxResult` | Calculates tax per manual rate or provider |
   538→| `extractInclusive(int $grossAmount, int $rateBasisPoints): int` | Extracts tax from gross (prices-include-tax mode) |
   539→| `addExclusive(int $netAmount, int $rateBasisPoints): int` | Adds tax to net amount |
   540→
   541→All math uses integers. Rates stored in basis points (1900 = 19%).
   542→
   543→#### Step 4.7: Pricing Engine
   544→
   545→**File to create:** `App\Services\PricingEngine`
   546→
   547→Method: `calculate(Checkout $checkout): PricingResult`
   548→
   549→**Pipeline steps:**
   550→1. Line subtotals: `unit_price * quantity` per line
   551→2. Cart subtotal: sum of line subtotals
   552→3. Discount: apply rules, allocate proportionally
   553→4. Discounted subtotal: subtotal minus discount
   554→5. Shipping: based on selected method/rate
   555→6. Tax: on discounted amounts (plus shipping if taxable)
   556→7. Total: discounted_subtotal + shipping + tax_total
   557→
   558→Result stored in `checkouts.totals_json` as snapshot.
   559→
   560→**Value objects to create:**
   561→
   562→| Type | Name | Fields |
   563→|------|------|--------|
   564→| Value Object | `App\ValueObjects\PricingResult` | subtotal, discount, shipping, taxLines, taxTotal, total, currency |
   565→| Value Object | `App\ValueObjects\TaxLine` | name, rate (basis points), amount (cents) |
   566→
   567→#### Step 4.8: Checkout State Machine
   568→
   569→**File to create:** `App\Services\CheckoutService`
   570→
   571→| Transition | Method | Requirements |
   572→|------------|--------|-------------|
   573→| `started` -> `addressed` | `setAddress(Checkout, array)` | Valid email, shipping address fields |
   574→| `addressed` -> `shipping_selected` | `setShippingMethod(Checkout, int $rateId)` | Rate belongs to applicable zone |
   575→| `shipping_selected` -> `payment_selected` | `selectPaymentMethod(Checkout, string)` | All data valid, reserve inventory |
   576→| `payment_selected` -> `completed` | `completeCheckout(Checkout, array)` | Payment confirmed, idempotent |
   577→| Any active -> `expired` | `expireCheckout(Checkout)` | Via scheduled job |
   578→
   579→**Files to create:**
   580→
   581→| Type | Name | Purpose |
   582→|------|------|---------|
   583→| Job | `App\Jobs\ExpireAbandonedCheckouts` | Runs every 15 minutes via `routes/console.php`; finds checkouts with `expires_at < now()` and active status; releases reserved inventory, transitions to `expired` |
   584→| Job | `App\Jobs\CleanupAbandonedCarts` | Runs daily; marks carts with `status = active` and `updated_at < 14 days ago` as `abandoned` |
   585→
   586→#### Step 4.9: Storefront Cart/Checkout UI
   587→
   588→**Files to create:**
   589→
   590→| Component | Description |
   591→|-----------|-------------|
   592→| `Storefront\CartDrawer` | Slide-out panel: line items, quantity controls, discount code input, subtotal, checkout button |
   593→| `Storefront\Cart\Show` | Full cart page with same functionality plus shipping estimate |
   594→| `Storefront\Checkout\Show` | Multi-step: Contact/Address -> Shipping method -> Payment. Stepper UI. |
   595→| `Storefront\Checkout\Confirmation` | Order confirmation: order number, items, totals, next steps |
   596→
   597→---
   598→
   599→### Phase 5: Payments, Orders, Fulfillment

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

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

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

#### Step 3.4: Storefront Livewire Components

**Files to create:**

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

#### Step 3.5: Navigation Service

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

| Method | Description |
|--------|-------------|
| `buildTree(NavigationMenu $menu): array` | Builds navigation trees from navigation_menus / navigation_items |
| `resolveUrl(NavigationItem $item): string` | Resolves URLs for page, collection, product type items |

Caches per store with 5-minute TTL.

---

### Phase 4: Cart, Checkout, Discounts, Shipping, Taxes

**Priority:** HIGH - core shopping flow.

#### Step 4.1: Migrations (Batch 4-5)

1. `create_carts_table` - FK: `store_id`, `customer_id`
2. `create_cart_lines_table` - FK: `cart_id`, `variant_id`
3. `create_checkouts_table` - FK: `store_id`, `cart_id`, `customer_id`
4. `create_shipping_zones_table` - FK: `store_id`
5. `create_shipping_rates_table` - FK: `zone_id`
6. `create_tax_settings_table` - PK: `store_id`
7. `create_discounts_table` - FK: `store_id`

#### Step 4.2: Models

**Files to create:**

| Type | Name | Purpose |
|------|------|---------|
| Model (with migration, factory, seeder) | `Cart` | Shopping cart entity |
| Model (with migration, factory, seeder) | `CartLine` | Individual cart line item |
| Model (with migration, factory, seeder) | `Checkout` | Checkout session entity |
| Model (with migration, factory, seeder) | `ShippingZone` | Shipping zone configuration |
| Model (with migration, factory, seeder) | `ShippingRate` | Shipping rate within a zone |
| Model (with migration, factory, seeder) | `TaxSettings` | Per-store tax configuration |
| Model (with migration, factory, seeder) | `Discount` | Discount code or automatic discount |

**Relationship definitions:**

| Model | Key Relationships |
|-------|-------------------|
| `Cart` | `belongsTo(Store)`, `belongsTo(Customer)`, `hasMany(CartLine)`, `hasMany(Checkout)` |
| `CartLine` | `belongsTo(Cart)`, `belongsTo(ProductVariant)` |
| `Checkout` | `belongsTo(Store)`, `belongsTo(Cart)`, `belongsTo(Customer)` |
| `ShippingZone` | `belongsTo(Store)`, `hasMany(ShippingRate)` |
| `ShippingRate` | `belongsTo(ShippingZone)` |
| `TaxSettings` | `belongsTo(Store)` |
| `Discount` | `belongsTo(Store)` |

**Enums to create:**

| Enum | Values |
|------|--------|
| `CartStatus` | `Active`, `Converted`, `Abandoned` |
| `CheckoutStatus` | `Started`, `Addressed`, `ShippingSelected`, `PaymentPending`, `Completed`, `Expired` |
| `DiscountType` | `Code`, `Automatic` |
| `DiscountValueType` | `Percent` (`percent`), `Fixed` (`fixed`), `FreeShipping` (`free_shipping`) -- PascalCase cases backed by lowercase string values stored in DB |
| `DiscountStatus` | `Draft`, `Active`, `Expired`, `Disabled` |
| `ShippingRateType` | `Flat`, `Weight`, `Price`, `Carrier` |
| `TaxMode` | `Manual`, `Provider` |

#### Step 4.3: Cart Service

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

| Method | Description |
|--------|-------------|
| `create(Store, ?Customer): Cart` | Creates cart with store currency, version 1 |
| `addLine(Cart, int $variantId, int $qty): CartLine` | Validates variant/product active, inventory, increments existing line or creates new |
| `updateLineQuantity(Cart, int $lineId, int $qty): CartLine` | Validates quantity, recalculates amounts |
| `removeLine(Cart, int $lineId): void` | Deletes line, increments version |
| `getOrCreateForSession(Store, ?Customer): Cart` | Session-based cart binding |
| `mergeOnLogin(Cart $guest, Cart $customer): Cart` | Merges guest cart into customer cart |

All mutations increment `cart_version`. API clients must send `expected_version` or receive 409 Conflict.

#### Step 4.4: Discount Service

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

| Method | Description |
|--------|-------------|
| `validate(string $code, Store $store, Cart $cart): Discount` | Validates code exists, is active, dates valid, usage limit not reached, minimum purchase met |
| `calculate(Discount $discount, int $subtotal, array $lines): DiscountResult` | Calculates amount, allocates proportionally across qualifying lines |

Case-insensitive code lookup. Throws `InvalidDiscountException` with specific reason codes.

#### Step 4.5: Shipping Calculator

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

| Method | Description |
|--------|-------------|
| `getAvailableRates(Store $store, array $address): Collection` | Matches zones by country/region, returns active rates |
| `calculate(ShippingRate $rate, Cart $cart): int` | Calculates cost based on rate type (flat/weight/price) |

Zone matching: checks `countries_json` for country code, then `regions_json` for region code.

#### Step 4.6: Tax Calculator

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

| Method | Description |
|--------|-------------|
| `calculate(int $amount, TaxSettings $settings, array $address): TaxResult` | Calculates tax per manual rate or provider |
| `extractInclusive(int $grossAmount, int $rateBasisPoints): int` | Extracts tax from gross (prices-include-tax mode) |
| `addExclusive(int $netAmount, int $rateBasisPoints): int` | Adds tax to net amount |

All math uses integers. Rates stored in basis points (1900 = 19%).

#### Step 4.7: Pricing Engine

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

Method: `calculate(Checkout $checkout): PricingResult`

**Pipeline steps:**
1. Line subtotals: `unit_price * quantity` per line
2. Cart subtotal: sum of line subtotals
3. Discount: apply rules, allocate proportionally
4. Discounted subtotal: subtotal minus discount
5. Shipping: based on selected method/rate
6. Tax: on discounted amounts (plus shipping if taxable)
7. Total: discounted_subtotal + shipping + tax_total

Result stored in `checkouts.totals_json` as snapshot.

**Value objects to create:**

| Type | Name | Fields |
|------|------|--------|
| Value Object | `App\ValueObjects\PricingResult` | subtotal, discount, shipping, taxLines, taxTotal, total, currency |
| Value Object | `App\ValueObjects\TaxLine` | name, rate (basis points), amount (cents) |

#### Step 4.8: Checkout State Machine

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

| Transition | Method | Requirements |
|------------|--------|-------------|
| `started` -> `addressed` | `setAddress(Checkout, array)` | Valid email, shipping address fields |
| `addressed` -> `shipping_selected` | `setShippingMethod(Checkout, int $rateId)` | Rate belongs to applicable zone |
| `shipping_selected` -> `payment_selected` | `selectPaymentMethod(Checkout, string)` | All data valid, reserve inventory |
| `payment_selected` -> `completed` | `completeCheckout(Checkout, array)` | Payment confirmed, idempotent |
| Any active -> `expired` | `expireCheckout(Checkout)` | Via scheduled job |

**Files to create:**

| Type | Name | Purpose |
|------|------|---------|
| Job | `App\Jobs\ExpireAbandonedCheckouts` | Runs every 15 minutes via `routes/console.php`; finds checkouts with `expires_at < now()` and active status; releases reserved inventory, transitions to `expired` |
| Job | `App\Jobs\CleanupAbandonedCarts` | Runs daily; marks carts with `status = active` and `updated_at < 14 days ago` as `abandoned` |

#### Step 4.9: Storefront Cart/Checkout UI

**Files to create:**

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

---

### Phase 5: Payments, Orders, Fulfillment


Now I have a good understanding of what Phase 11 (Polish) and Phase 12 (Full Test Suite) need. Let me also look at what seeders currently exist and what the current state of the codebase is.

Phase 11 needs:
1. Comprehensive seed data (per spec 07)
2. Error pages (404, 503)
3. Dark mode support
4. Accessibility (skip links, ARIA labels, focus management)
5. Responsive design
6. Structured logging

Phase 12 needs:
1. All Pest tests pass
2. Playwright E2E tests (but the spec says to use Pest Browser testing with Playwright)

3. Code style (Pint)
4. Fresh migration + seed

I'm re-reading the original request and realizing I need to use the Playwright MCP tools to manually walk through the app and verify all acceptance criteria are met. So my approach is to first examine the existing seeders and understand what's missing, then build out comprehensive seed data for Phase 11, and finally use Playwright to test the app end-to-end.


Let me check the current state of seeders and existing code to understand what Phase 11 needs.


No files found


1→<?php
     2→
     3→namespace Database\Seeders;
     4→
     5→use App\Enums\NavigationItemType;
     6→use App\Enums\PageStatus;
     7→use App\Enums\ThemeStatus;
     8→use App\Models\NavigationItem;
     9→use App\Models\NavigationMenu;
    10→use App\Models\Organization;
    11→use App\Models\Page;
    12→use App\Models\Store;
    13→use App\Models\StoreDomain;
    14→use App\Models\StoreSettings;
    15→use App\Models\Theme;
    16→use App\Models\ThemeSettings;
    17→use App\Models\User;
    18→use Illuminate\Database\Seeder;
    19→
    20→class DatabaseSeeder extends Seeder
    21→{
    22→    /**
    23→     * Seed the application's database.
    24→     */
    25→    public function run(): void
    26→    {
    27→        $organization = Organization::create([
    28→            'name' => 'Default Organization',
    29→            'billing_email' => 'billing@shop.test',
    30→        ]);
    31→
    32→        $store = Store::create([
    33→            'organization_id' => $organization->id,
    34→            'name' => 'Default Store',
    35→            'handle' => 'default-store',
    36→            'status' => 'active',
    37→            'default_currency' => 'USD',
    38→            'default_locale' => 'en',
    39→            'timezone' => 'UTC',
    40→        ]);
    41→
    42→        StoreDomain::create([
    43→            'store_id' => $store->id,
    44→            'hostname' => 'shop.test',
    45→            'type' => 'storefront',
    46→            'is_primary' => true,
    47→            'tls_mode' => 'managed',
    48→        ]);
    49→
    50→        StoreSettings::create([
    51→            'store_id' => $store->id,
    52→            'settings_json' => [],
    53→        ]);
    54→
    55→        $admin = User::factory()->create([
    56→            'name' => 'Admin User',
    57→            'email' => 'admin@shop.test',
    58→            'status' => 'active',
    59→        ]);
    60→
    61→        $store->users()->attach($admin->id, ['role' => 'owner']);
    62→
    63→        // Create default theme
    64→        $theme = Theme::create([
    65→            'store_id' => $store->id,
    66→            'name' => 'Default Theme',
    67→            'version' => '1.0.0',
    68→            'status' => ThemeStatus::Published,
    69→            'published_at' => now()->toIso8601String(),
    70→        ]);
    71→
    72→        ThemeSettings::create([
    73→            'theme_id' => $theme->id,
    74→            'settings_json' => [
    75→                'announcement_bar' => [
    76→                    'enabled' => true,
    77→                    'text' => 'Free shipping on orders over $50',
    78→                    'link' => '/collections',
    79→                    'background_color' => '#1a1a1a',
    80→                ],
    81→                'sticky_header' => true,
    82→                'logo_url' => null,
    83→                'colors' => [
    84→                    'primary' => '#3b82f6',
    85→                    'secondary' => '#6b7280',
    86→                    'accent' => '#f59e0b',
    87→                ],
    88→                'social_links' => [
    89→                    'facebook' => 'https://facebook.com',
    90→                    'instagram' => 'https://instagram.com',
    91→                    'twitter' => 'https://x.com',
    92→                    'tiktok' => null,
    93→                    'youtube' => null,
    94→                ],
    95→                'dark_mode' => 'system',
    96→                'home_sections' => [
    97→                    ['type' => 'hero', 'enabled' => true],
    98→                    ['type' => 'featured_collections', 'enabled' => true],
    99→                    ['type' => 'featured_products', 'enabled' => true],
   100→                    ['type' => 'newsletter', 'enabled' => true],
   101→                    ['type' => 'rich_text', 'enabled' => false],
   102→                ],
   103→                'hero' => [
   104→                    'heading' => 'Welcome to Default Store',
   105→                    'subheading' => 'Discover amazing products at great prices.',
   106→                    'cta_text' => 'Shop now',
   107→                    'cta_link' => '/collections',
   108→                    'background_image' => null,
   109→                ],
   110→                'footer' => [
   111→                    'store_address' => '123 Commerce St, Shop City, SC 12345',
   112→                    'contact_email' => 'hello@shop.test',
   113→                ],
   114→            ],
   115→        ]);
   116→
   117→        // Create sample pages
   118→        $aboutPage = Page::create([
   119→            'store_id' => $store->id,
   120→            'title' => 'About Us',
   121→            'handle' => 'about',
   122→            'body_html' => '
Our Story

We are a passionate team dedicated to bringing you the best products at the best prices. Our mission is to make quality products accessible to everyone.

Our Values

Quality, affordability, and customer satisfaction are at the heart of everything we do.
',
   123→            'status' => PageStatus::Published,
   124→            'published_at' => now()->toIso8601String(),
   125→        ]);
   126→
   127→        $contactPage = Page::create([
   128→            'store_id' => $store->id,
   129→            'title' => 'Contact Us',
   130→            'handle' => 'contact',
   131→            'body_html' => '
Get in Touch

We would love to hear from you! Whether you have a question about products, orders, or anything else, our team is ready to answer all your questions.

Email

hello@shop.test

Address

123 Commerce St, Shop City, SC 12345
',
   132→            'status' => PageStatus::Published,
   133→            'published_at' => now()->toIso8601String(),
   134→        ]);
   135→
   136→        // Create Main Menu navigation
   137→        $mainMenu = NavigationMenu::create([
   138→            'store_id' => $store->id,
   139→            'handle' => 'main-menu',
   140→            'title' => 'Main Menu',
   141→        ]);
   142→
   143→        NavigationItem::create([
   144→            'menu_id' => $mainMenu->id,
   145→            'type' => NavigationItemType::Link,
   146→            'label' => 'Home',
   147→            'url' => '/',
   148→            'position' => 0,
   149→        ]);
   150→
   151→        NavigationItem::create([
   152→            'menu_id' => $mainMenu->id,
   153→            'type' => NavigationItemType::Link,
   154→            'label' => 'Collections',
   155→            'url' => '/collections',
   156→            'position' => 1,
   157→        ]);
   158→
   159→        NavigationItem::create([
   160→            'menu_id' => $mainMenu->id,
   161→            'type' => NavigationItemType::Page,
   162→            'label' => 'About',
   163→            'resource_id' => $aboutPage->id,
   164→            'position' => 2,
   165→        ]);
   166→
   167→        NavigationItem::create([
   168→            'menu_id' => $mainMenu->id,
   169→            'type' => NavigationItemType::Page,
   170→            'label' => 'Contact',
   171→            'resource_id' => $contactPage->id,
   172→            'position' => 3,
   173→        ]);
   174→
   175→        // Create Footer Menu navigation
   176→        $footerMenu = NavigationMenu::create([
   177→            'store_id' => $store->id,
   178→            'handle' => 'footer-menu',
   179→            'title' => 'Footer Menu',
   180→        ]);
   181→
   182→        NavigationItem::create([
   183→            'menu_id' => $footerMenu->id,
   184→            'type' => NavigationItemType::Page,
   185→            'label' => 'About Us',
   186→            'resource_id' => $aboutPage->id,
   187→            'position' => 0,
   188→        ]);
   189→
   190→        NavigationItem::create([
   191→            'menu_id' => $footerMenu->id,
   192→            'type' => NavigationItemType::Page,
   193→            'label' => 'Contact Us',
   194→            'resource_id' => $contactPage->id,
   195→            'position' => 1,
   196→        ]);
   197→
   198→        NavigationItem::create([
   199→            'menu_id' => $footerMenu->id,
   200→            'type' => NavigationItemType::Link,
   201→            'label' => 'Search',
   202→            'url' => '/search',
   203→            'position' => 2,
   204→        ]);
   205→    }
   206→}
   207→

<?php

namespace Database\Seeders;

use App\Enums\NavigationItemType;
use App\Enums\PageStatus;
use App\Enums\ThemeStatus;
use App\Models\NavigationItem;
use App\Models\NavigationMenu;
use App\Models\Organization;
use App\Models\Page;
use App\Models\Store;
use App\Models\StoreDomain;
use App\Models\StoreSettings;
use App\Models\Theme;
use App\Models\ThemeSettings;
use App\Models\User;
use Illuminate\Database\Seeder;

class DatabaseSeeder extends Seeder
{
    /**
     * Seed the application's database.
     */
    public function run(): void
    {
        $organization = Organization::create([
            'name' => 'Default Organization',
            'billing_email' => 'billing@shop.test',
        ]);

        $store = Store::create([
            'organization_id' => $organization->id,
            'name' => 'Default Store',
            'handle' => 'default-store',
            'status' => 'active',
            'default_currency' => 'USD',
            'default_locale' => 'en',
            'timezone' => 'UTC',
        ]);

        StoreDomain::create([
            'store_id' => $store->id,
            'hostname' => 'shop.test',
            'type' => 'storefront',
            'is_primary' => true,
            'tls_mode' => 'managed',
        ]);

        StoreSettings::create([
            'store_id' => $store->id,
            'settings_json' => [],
        ]);

        $admin = User::factory()->create([
            'name' => 'Admin User',
            'email' => 'admin@shop.test',
            'status' => 'active',
        ]);

        $store->users()->attach($admin->id, ['role' => 'owner']);

        // Create default theme
        $theme = Theme::create([
            'store_id' => $store->id,
            'name' => 'Default Theme',
            'version' => '1.0.0',
            'status' => ThemeStatus::Published,
            'published_at' => now()->toIso8601String(),
        ]);

        ThemeSettings::create([
            'theme_id' => $theme->id,
            'settings_json' => [
                'announcement_bar' => [
                    'enabled' => true,
                    'text' => 'Free shipping on orders over $50',
                    'link' => '/collections',
                    'background_color' => '#1a1a1a',
                ],
                'sticky_header' => true,
                'logo_url' => null,
                'colors' => [
                    'primary' => '#3b82f6',
                    'secondary' => '#6b7280',
                    'accent' => '#f59e0b',
                ],
                'social_links' => [
                    'facebook' => 'https://facebook.com',
                    'instagram' => 'https://instagram.com',
                    'twitter' => 'https://x.com',
                    'tiktok' => null,
                    'youtube' => null,
                ],
                'dark_mode' => 'system',
                'home_sections' => [
                    ['type' => 'hero', 'enabled' => true],
                    ['type' => 'featured_collections', 'enabled' => true],
                    ['type' => 'featured_products', 'enabled' => true],
                    ['type' => 'newsletter', 'enabled' => true],
                    ['type' => 'rich_text', 'enabled' => false],
                ],
                'hero' => [
                    'heading' => 'Welcome to Default Store',
                    'subheading' => 'Discover amazing products at great prices.',
                    'cta_text' => 'Shop now',
                    'cta_link' => '/collections',
                    'background_image' => null,
                ],
                'footer' => [
                    'store_address' => '123 Commerce St, Shop City, SC 12345',
                    'contact_email' => 'hello@shop.test',
                ],
            ],
        ]);

        // Create sample pages
        $aboutPage = Page::create([
            'store_id' => $store->id,
            'title' => 'About Us',
            'handle' => 'about',
            'body_html' => '
Our Story

We are a passionate team dedicated to bringing you the best products at the best prices. Our mission is to make quality products accessible to everyone.

Our Values

Quality, affordability, and customer satisfaction are at the heart of everything we do.
',
            'status' => PageStatus::Published,
            'published_at' => now()->toIso8601String(),
        ]);

        $contactPage = Page::create([
            'store_id' => $store->id,
            'title' => 'Contact Us',
            'handle' => 'contact',
            'body_html' => '
Get in Touch

We would love to hear from you! Whether you have a question about products, orders, or anything else, our team is ready to answer all your questions.

Email

hello@shop.test

Address

123 Commerce St, Shop City, SC 12345
',
            'status' => PageStatus::Published,
            'published_at' => now()->toIso8601String(),
        ]);

        // Create Main Menu navigation
        $mainMenu = NavigationMenu::create([
            'store_id' => $store->id,
            'handle' => 'main-menu',
            'title' => 'Main Menu',
        ]);

        NavigationItem::create([
            'menu_id' => $mainMenu->id,
            'type' => NavigationItemType::Link,
            'label' => 'Home',
            'url' => '/',
            'position' => 0,
        ]);

        NavigationItem::create([
            'menu_id' => $mainMenu->id,
            'type' => NavigationItemType::Link,
            'label' => 'Collections',
            'url' => '/collections',
            'position' => 1,
        ]);

        NavigationItem::create([
            'menu_id' => $mainMenu->id,
            'type' => NavigationItemType::Page,
            'label' => 'About',
            'resource_id' => $aboutPage->id,
            'position' => 2,
        ]);

        NavigationItem::create([
            'menu_id' => $mainMenu->id,
            'type' => NavigationItemType::Page,
            'label' => 'Contact',
            'resource_id' => $contactPage->id,
            'position' => 3,
        ]);

        // Create Footer Menu navigation
        $footerMenu = NavigationMenu::create([
            'store_id' => $store->id,
            'handle' => 'footer-menu',
            'title' => 'Footer Menu',
        ]);

        NavigationItem::create([
            'menu_id' => $footerMenu->id,
            'type' => NavigationItemType::Page,
            'label' => 'About Us',
            'resource_id' => $aboutPage->id,
            'position' => 0,
        ]);

        NavigationItem::create([
            'menu_id' => $footerMenu->id,
            'type' => NavigationItemType::Page,
            'label' => 'Contact Us',
            'resource_id' => $contactPage->id,
            'position' => 1,
        ]);

        NavigationItem::create([
            'menu_id' => $footerMenu->id,
            'type' => NavigationItemType::Link,
            'label' => 'Search',
            'url' => '/search',
            'position' => 2,
        ]);
    }
}


1→<!DOCTYPE html>
     2→getLocale()) }}" class="dark">
     3→
     4→        @include('partials.head')
     5→
     6→
     7→        <flux:sidebar sticky collapsible="mobile" class="bg-zinc-50 dark:bg-zinc-900 border-e border-zinc-200 dark:border-zinc-700">
     8→            <flux:sidebar.header>
     9→                <flux:sidebar.brand
    10→                    href="{{ route('admin.dashboard') }}"
    11→                    name="{{ app()->bound('current_store') ? app('current_store')->name : config('app.name') }}"
    12→                    wire:navigate
    13→                />
    14→                <flux:sidebar.collapse class="lg:hidden" />
    15→            </flux:sidebar.header>
    16→
    17→            <flux:sidebar.nav>
    18→                <flux:sidebar.item icon="home" :href="route('admin.dashboard')" :current="request()->routeIs('admin.dashboard')" wire:navigate>
    19→                    {{ __('Dashboard') }}
    20→                </flux:sidebar.item>
    21→
    22→                <flux:sidebar.group expandable heading="Products" class="grid">
    23→                    <flux:sidebar.item :href="route('admin.products.index')" :current="request()->routeIs('admin.products.*')" wire:navigate>
    24→                        {{ __('Products') }}
    25→                    </flux:sidebar.item>
    26→                    <flux:sidebar.item :href="route('admin.collections.index')" :current="request()->routeIs('admin.collections.*')" wire:navigate>
    27→                        {{ __('Collections') }}
    28→                    </flux:sidebar.item>
    29→                    <flux:sidebar.item :href="route('admin.inventory.index')" :current="request()->routeIs('admin.inventory.*')" wire:navigate>
    30→                        {{ __('Inventory') }}
    31→                    </flux:sidebar.item>
    32→                </flux:sidebar.group>
    33→
    34→                <flux:sidebar.item icon="shopping-bag" :href="route('admin.orders.index')" :current="request()->routeIs('admin.orders.*')" wire:navigate>
    35→                    {{ __('Orders') }}
    36→                </flux:sidebar.item>
    37→
    38→                <flux:sidebar.item icon="users" :href="route('admin.customers.index')" :current="request()->routeIs('admin.customers.*')" wire:navigate>
    39→                    {{ __('Customers') }}
    40→                </flux:sidebar.item>
    41→
    42→                <flux:sidebar.item icon="tag" :href="route('admin.discounts.index')" :current="request()->routeIs('admin.discounts.*')" wire:navigate>
    43→                    {{ __('Discounts') }}
    44→                </flux:sidebar.item>
    45→
    46→                <flux:sidebar.group expandable heading="Content" class="grid">
    47→                    <flux:sidebar.item :href="route('admin.pages.index')" :current="request()->routeIs('admin.pages.*')" wire:navigate>
    48→                        {{ __('Pages') }}
    49→                    </flux:sidebar.item>
    50→                    <flux:sidebar.item :href="route('admin.navigation.index')" :current="request()->routeIs('admin.navigation.*')" wire:navigate>
    51→                        {{ __('Navigation') }}
    52→                    </flux:sidebar.item>
    53→                    <flux:sidebar.item :href="route('admin.themes.index')" :current="request()->routeIs('admin.themes.*')" wire:navigate>
    54→                        {{ __('Themes') }}
    55→                    </flux:sidebar.item>
    56→                </flux:sidebar.group>
    57→            </flux:sidebar.nav>
    58→
    59→            <flux:sidebar.spacer />
    60→
    61→            <flux:sidebar.nav>
    62→                <flux:sidebar.item icon="chart-bar-square" :href="route('admin.analytics.index')" :current="request()->routeIs('admin.analytics.*')" wire:navigate>
    63→                    {{ __('Analytics') }}
    64→                </flux:sidebar.item>
    65→                <flux:sidebar.item icon="cog-6-tooth" :href="route('admin.settings.index')" :current="request()->routeIs('admin.settings.*')" wire:navigate>
    66→                    {{ __('Settings') }}
    67→                </flux:sidebar.item>
    68→            </flux:sidebar.nav>
    69→
    70→            @auth
    71→                <flux:dropdown position="top" align="start" class="max-lg:hidden">
    72→                    <flux:sidebar.profile :initials="auth()->user()->initials()" :name="auth()->user()->name" />
    73→                    <flux:menu>
    74→                        @if(auth()->user()->stores->count() > 1)
    75→                            <flux:menu.radio.group>
    76→                                @foreach(auth()->user()->stores as $storeOption)
    77→                                    <flux:menu.radio
    78→                                        wire:click="$dispatch('switch-store', { storeId: {{ $storeOption->id }} })"
    79→                                        :checked="app()->bound('current_store') && app('current_store')->id === $storeOption->id"
    80→                                    >
    81→                                        {{ $storeOption->name }}
    82→                                    </flux:menu.radio>
    83→                                @endforeach
    84→                            </flux:menu.radio.group>
    85→                            <flux:menu.separator />
    86→                        @endif
    87→                        <flux:menu.item icon="arrow-right-start-on-rectangle" href="{{ route('admin.logout') }}">
    88→                            {{ __('Log Out') }}
    89→                        </flux:menu.item>
    90→                    </flux:menu>
    91→                </flux:dropdown>
    92→            @endauth
    93→        </flux:sidebar>
    94→
    95→
    96→        <flux:header class="lg:hidden">
    97→            <flux:sidebar.toggle class="lg:hidden" icon="bars-2" inset="left" />
    98→
    99→            <flux:spacer />
   100→
   101→            @auth
   102→                <flux:dropdown position="top" align="end">
   103→                    <flux:profile
   104→                        :initials="auth()->user()->initials()"
   105→                        icon-trailing="chevron-down"
   106→                    />
   107→                    <flux:menu>
   108→                        <flux:menu.radio.group>
   109→

   110→

   111→                                    <flux:avatar :initials="auth()->user()->initials()" />
   112→

   113→                                        <flux:heading class="truncate">{{ auth()->user()->name }}</flux:heading>
   114→                                        <flux:text class="truncate">{{ auth()->user()->email }}</flux:text>
   115→

   116→

   117→

   118→                        </flux:menu.radio.group>
   119→                        <flux:menu.separator />
   120→                        <flux:menu.item icon="arrow-right-start-on-rectangle" href="{{ route('admin.logout') }}">
   121→                            {{ __('Log Out') }}
   122→                        </flux:menu.item>
   123→                    </flux:menu>
   124→                </flux:dropdown>
   125→            @endauth
   126→        </flux:header>
   127→
   128→        <flux:main>
   129→            @if(session()->has('toast'))
   130→
 show = false, 4000)"
   134→                    x-transition
   135→                    class="mb-4"
   136→                >
   137→                    <flux:callout variant="{{ session('toast.type', 'secondary') }}">
   138→                        <flux:callout.heading>{{ session('toast.message') }}</flux:callout.heading>
   139→                    </flux:callout>
   140→

   141→            @endif
   142→
   143→            {{ $slot }}
   144→        </flux:main>
   145→
   146→        @fluxScripts
   147→
   148→
   149→

<!DOCTYPE html>
getLocale()) }}" class="dark">

        @include('partials.head')

        <flux:sidebar sticky collapsible="mobile" class="bg-zinc-50 dark:bg-zinc-900 border-e border-zinc-200 dark:border-zinc-700">
            <flux:sidebar.header>
                <flux:sidebar.brand
                    href="{{ route('admin.dashboard') }}"
                    name="{{ app()->bound('current_store') ? app('current_store')->name : config('app.name') }}"
                    wire:navigate
                />
                <flux:sidebar.collapse class="lg:hidden" />
            </flux:sidebar.header>

            <flux:sidebar.nav>
                <flux:sidebar.item icon="home" :href="route('admin.dashboard')" :current="request()->routeIs('admin.dashboard')" wire:navigate>
                    {{ __('Dashboard') }}
                </flux:sidebar.item>

                <flux:sidebar.group expandable heading="Products" class="grid">
                    <flux:sidebar.item :href="route('admin.products.index')" :current="request()->routeIs('admin.products.*')" wire:navigate>
                        {{ __('Products') }}
                    </flux:sidebar.item>
                    <flux:sidebar.item :href="route('admin.collections.index')" :current="request()->routeIs('admin.collections.*')" wire:navigate>
                        {{ __('Collections') }}
                    </flux:sidebar.item>
                    <flux:sidebar.item :href="route('admin.inventory.index')" :current="request()->routeIs('admin.inventory.*')" wire:navigate>
                        {{ __('Inventory') }}
                    </flux:sidebar.item>
                </flux:sidebar.group>

                <flux:sidebar.item icon="shopping-bag" :href="route('admin.orders.index')" :current="request()->routeIs('admin.orders.*')" wire:navigate>
                    {{ __('Orders') }}
                </flux:sidebar.item>

                <flux:sidebar.item icon="users" :href="route('admin.customers.index')" :current="request()->routeIs('admin.customers.*')" wire:navigate>
                    {{ __('Customers') }}
                </flux:sidebar.item>

                <flux:sidebar.item icon="tag" :href="route('admin.discounts.index')" :current="request()->routeIs('admin.discounts.*')" wire:navigate>
                    {{ __('Discounts') }}
                </flux:sidebar.item>

                <flux:sidebar.group expandable heading="Content" class="grid">
                    <flux:sidebar.item :href="route('admin.pages.index')" :current="request()->routeIs('admin.pages.*')" wire:navigate>
                        {{ __('Pages') }}
                    </flux:sidebar.item>
                    <flux:sidebar.item :href="route('admin.navigation.index')" :current="request()->routeIs('admin.navigation.*')" wire:navigate>
                        {{ __('Navigation') }}
                    </flux:sidebar.item>
                    <flux:sidebar.item :href="route('admin.themes.index')" :current="request()->routeIs('admin.themes.*')" wire:navigate>
                        {{ __('Themes') }}
                    </flux:sidebar.item>
                </flux:sidebar.group>
            </flux:sidebar.nav>

            <flux:sidebar.spacer />

            <flux:sidebar.nav>
                <flux:sidebar.item icon="chart-bar-square" :href="route('admin.analytics.index')" :current="request()->routeIs('admin.analytics.*')" wire:navigate>
                    {{ __('Analytics') }}
                </flux:sidebar.item>
                <flux:sidebar.item icon="cog-6-tooth" :href="route('admin.settings.index')" :current="request()->routeIs('admin.settings.*')" wire:navigate>
                    {{ __('Settings') }}
                </flux:sidebar.item>
            </flux:sidebar.nav>

            @auth
                <flux:dropdown position="top" align="start" class="max-lg:hidden">
                    <flux:sidebar.profile :initials="auth()->user()->initials()" :name="auth()->user()->name" />
                    <flux:menu>
                        @if(auth()->user()->stores->count() > 1)
                            <flux:menu.radio.group>
                                @foreach(auth()->user()->stores as $storeOption)
                                    <flux:menu.radio
                                        wire:click="$dispatch('switch-store', { storeId: {{ $storeOption->id }} })"
                                        :checked="app()->bound('current_store') && app('current_store')->id === $storeOption->id"
                                    >
                                        {{ $storeOption->name }}
                                    </flux:menu.radio>
                                @endforeach
                            </flux:menu.radio.group>
                            <flux:menu.separator />
                        @endif
                        <flux:menu.item icon="arrow-right-start-on-rectangle" href="{{ route('admin.logout') }}">
                            {{ __('Log Out') }}
                        </flux:menu.item>
                    </flux:menu>
                </flux:dropdown>
            @endauth
        </flux:sidebar>

        <flux:header class="lg:hidden">
            <flux:sidebar.toggle class="lg:hidden" icon="bars-2" inset="left" />

            <flux:spacer />

            @auth
                <flux:dropdown position="top" align="end">
                    <flux:profile
                        :initials="auth()->user()->initials()"
                        icon-trailing="chevron-down"
                    />
                    <flux:menu>
                        <flux:menu.radio.group>

                                    <flux:avatar :initials="auth()->user()->initials()" />

                                        <flux:heading class="truncate">{{ auth()->user()->name }}</flux:heading>
                                        <flux:text class="truncate">{{ auth()->user()->email }}</flux:text>

                        </flux:menu.radio.group>
                        <flux:menu.separator />
                        <flux:menu.item icon="arrow-right-start-on-rectangle" href="{{ route('admin.logout') }}">
                            {{ __('Log Out') }}
                        </flux:menu.item>
                    </flux:menu>
                </flux:dropdown>
            @endauth
        </flux:header>

        <flux:main>
            @if(session()->has('toast'))

 show = false, 4000)"
                    x-transition
                    class="mb-4"
                >
                    <flux:callout variant="{{ session('toast.type', 'secondary') }}">
                        <flux:callout.heading>{{ session('toast.message') }}</flux:callout.heading>
                    </flux:callout>

            @endif

            {{ $slot }}
        </flux:main>

        @fluxScripts


I can see that the current DatabaseSeeder is very minimal - it only creates a "Default Store" at "shop.test" with basic pages and navigation. The spec requires a comprehensive seeder with "Acme Fashion" and "Acme Electronics" stores, 20+ products, customers, orders, etc.

Let me check the storefront layout for current state of dark mode, accessibility, etc.


The current DatabaseSeeder is minimal - just a "Default Store" at "shop.test". The spec requires comprehensive seed data with "Acme Fashion" and "Acme Electronics" stores, 20+ products, 15 orders, 10 customers, etc.

Let me now check the storefront layout and existing components to understand the polish work needed.


1→

     2→    @foreach($sections as $section)
     3→        @if(!empty($section['enabled']))
     4→            @switch($section['type'])
     5→                @case('hero')
     6→                    {{-- Hero Banner --}}
     7→

     8→                        @if(!empty($hero['background_image']))
     9→
    10→                        @endif
    11→

    12→

    13→

    14→                                {{ $hero['heading'] ?? 'Welcome to our store' }}
    15→

    16→                            @if(!empty($hero['subheading']))
    17→

    18→                                    {{ $hero['subheading'] }}
    19→

    20→                            @endif
    21→                            @if(!empty($hero['cta_text']))
    22→
    24→                                    {{ $hero['cta_text'] }}
    25→
    26→                            @endif
    27→

    28→

    29→                    @break
    30→
    31→                @case('featured_collections')
    32→                    {{-- Featured Collections --}}
    33→                    @if($featuredCollections->isNotEmpty())
    34→

    35→

    36→                                Collections
    37→

    38→

    39→                                @foreach($featuredCollections as $collection)
    40→                                    handle) }}"
    41→                                       class="group relative block overflow-hidden rounded-lg aspect-[3/4]">
    42→

    43→

    44→

    45→
{{ $collection->title }}

    46→
    47→                                                Shop now
    48→
    49→

    50→
    51→                                @endforeach
    52→

    53→

    54→                    @endif
    55→                    @break
    56→
    57→                @case('featured_products')
    58→                    {{-- Featured Products --}}
    59→                    @if($featuredProducts->isNotEmpty())
    60→

    61→

    62→                                Featured Products
    63→

    64→

    65→                                @foreach($featuredProducts as $product)
    66→                                    <x-storefront.product-card :product="$product" />
    67→                                @endforeach
    68→

    69→

    70→                    @endif
    71→                    @break
    72→
    73→                @case('newsletter')
    74→                    {{-- Newsletter --}}
    75→

    76→

    77→

    78→                                Stay in the loop
    79→

    80→

    81→                                Subscribe for exclusive offers and updates.
    82→

    83→                            <form class="mt-6 flex gap-3" onsubmit="event.preventDefault()">
    84→                                Email address
    85→                                <input type="email"
    86→                                       id="newsletter-email"
    87→                                       placeholder="Enter your email"
    88→                                       required
    89→                                       class="flex-1 rounded-md border border-zinc-300 dark:border-zinc-600 bg-white dark:bg-zinc-800 px-4 py-2.5 text-sm text-zinc-900 dark:text-white placeholder-zinc-400 focus:outline-none focus:ring-2 focus:ring-blue-500">
    90→                                <button type="submit"
    91→                                        class="shrink-0 rounded-md bg-blue-600 px-5 py-2.5 text-sm font-semibold text-white hover:bg-blue-700 transition-colors">
    92→                                    Subscribe
    93→                                </button>
    94→                            </form>
    95→

    96→

    97→                    @break
    98→
    99→                @case('rich_text')
   100→                    {{-- Rich Text --}}
   101→

   102→

   103→                            {!! $themeSettings->get('rich_text_content', '') !!}
   104→

   105→

   106→                    @break
   107→            @endswitch
   108→        @endif
   109→    @endforeach
   110→

   111→

    @foreach($sections as $section)
        @if(!empty($section['enabled']))
            @switch($section['type'])
                @case('hero')
                    {{-- Hero Banner --}}

                        @if(!empty($hero['background_image']))

                        @endif

                                {{ $hero['heading'] ?? 'Welcome to our store' }}

                            @if(!empty($hero['subheading']))

                                    {{ $hero['subheading'] }}

                            @endif
                            @if(!empty($hero['cta_text']))

                                    {{ $hero['cta_text'] }}

                            @endif

                    @break

                @case('featured_collections')
                    {{-- Featured Collections --}}
                    @if($featuredCollections->isNotEmpty())

                                Collections

                                @foreach($featuredCollections as $collection)
                                    handle) }}"
                                       class="group relative block overflow-hidden rounded-lg aspect-[3/4]">

{{ $collection->title }}

                                                Shop now

                                @endforeach

                    @endif
                    @break

                @case('featured_products')
                    {{-- Featured Products --}}
                    @if($featuredProducts->isNotEmpty())

                                Featured Products

                                @foreach($featuredProducts as $product)
                                    <x-storefront.product-card :product="$product" />
                                @endforeach

                    @endif
                    @break

                @case('newsletter')
                    {{-- Newsletter --}}

                                Stay in the loop

                                Subscribe for exclusive offers and updates.

                            <form class="mt-6 flex gap-3" onsubmit="event.preventDefault()">
                                Email address
                                <input type="email"
                                       id="newsletter-email"
                                       placeholder="Enter your email"
                                       required
                                       class="flex-1 rounded-md border border-zinc-300 dark:border-zinc-600 bg-white dark:bg-zinc-800 px-4 py-2.5 text-sm text-zinc-900 dark:text-white placeholder-zinc-400 focus:outline-none focus:ring-2 focus:ring-blue-500">
                                <button type="submit"
                                        class="shrink-0 rounded-md bg-blue-600 px-5 py-2.5 text-sm font-semibold text-white hover:bg-blue-700 transition-colors">
                                    Subscribe
                                </button>
                            </form>

                    @break

                @case('rich_text')
                    {{-- Rich Text --}}

                            {!! $themeSettings->get('rich_text_content', '') !!}

                    @break
            @endswitch
        @endif
    @endforeach


Now I have a clear picture. Phase 11 is extensive -- the biggest piece is the comprehensive DatabaseSeeder. Let me launch parallel agents for the major Phase 11 workstreams.


Task

Phase 11a: Comprehensive seeders

Prompt:
You are implementing Phase 11 of an e-commerce shop. Your task is to create the COMPREHENSIVE DATABASE SEEDER according to the spec.

**WORKING DIRECTORY:** /Users/fabianwesner/Herd/shop

**IMPORTANT RULES:**
- PHP 8.4 / Laravel 12 / Livewire v4
- All monetary amounts as INTEGER in minor units (cents)
- Use `firstOrCreate` or `updateOrCreate` for idempotency
- Wrap each seeder's work in a database transaction
- NEVER use em-dash (—) in text, use regular dash (-) instead

**CURRENT STATE:** The DatabaseSeeder at `database/seeders/DatabaseSeeder.php` is minimal - just a "Default Store" at "shop.test". It needs to be completely rewritten to match the spec.

**WHAT YOU NEED TO CREATE:**

Replace the entire DatabaseSeeder with a comprehensive seeder that creates ALL the following data. Create individual seeder classes where appropriate but the main DatabaseSeeder should orchestrate everything.

The seeder must create (in this order):

1. **Organization**: "Acme Corp", billing@acme.test
2. **Stores**:
   - "Acme Fashion" (handle: acme-fashion, EUR, Europe/Berlin)
   - "Acme Electronics" (handle: acme-electronics, EUR, Europe/Berlin)
3. **Store Domains**:
   - acme-fashion.test (storefront, primary)
   - admin.acme-fashion.test (admin)
   - acme-electronics.test (storefront, primary)
4. **Users**: admin@acme.test (Admin User), staff@acme.test (Staff User), support@acme.test (Support User), manager@acme.test (Store Manager), admin2@acme.test (Admin Two) - all password "password", status "active"
5. **Store Users**: admin@acme.test=owner on Fashion, staff=staff, support=support, manager=admin on Fashion; admin2=owner on Electronics
6. **Store Settings**: Per-store settings_json with store_name, contact_email, order_number_prefix "#", order_number_start 1001/5001
7. **Tax Settings**: Both stores: mode=manual, prices_include_tax=1, default_rate_bps=1900
8. **Shipping Zones/Rates**: Acme Fashion: Domestic(DE) with Standard 499/Express 999, EU(AT,FR,IT,ES,NL,BE,PL) Standard 899, Rest of World(US,GB,CA,AU) International 1499. Acme Electronics: Germany(DE) Standard 0 (free).
9. **Collections**: Acme Fashion: New Arrivals, T-Shirts, Pants & Jeans, Sale. Acme Electronics: Featured, Accessories.
10. **Products** (Acme Fashion - 20 products with full options, variants, inventory):
    - Product 1: Classic Cotton T-Shirt (handle: classic-cotton-t-shirt, vendor: Acme Basics, type: T-Shirts, tags: ["new","popular"], price: 2499, Options: Size S/M/L/XL x Color White/Black/Navy = 12 variants, inventory 15 each deny, collections: New Arrivals + T-Shirts)
    - Product 2: Premium Slim Fit Jeans (handle: premium-slim-fit-jeans, vendor: Acme Denim, type: Pants, tags: ["new","sale"], price: 7999, compare_at: 9999, Options: Size 28/30/32/34/36 x Color Blue/Black = 10 variants, inventory 8 each, collections: New Arrivals + Pants & Jeans + Sale)
    - Product 3: Organic Hoodie (handle: organic-hoodie, vendor: Acme Basics, type: Hoodies, tags: ["new","trending"], price: 5999, Options: Size S/M/L/XL = 4 variants, inventory 20 each, collections: New Arrivals)
    - Product 4: Leather Belt (handle: leather-belt, vendor: Acme Accessories, type: Accessories, tags: ["popular"], price: 3499, Options: Size S/M+L/XL x Color Brown/Black = 4 variants, inventory 25 each)
    - Product 5: Running Sneakers (handle: running-sneakers, vendor: Acme Sport, type: Shoes, tags: ["trending"], price: 11999, Options: Size EU38-44 x Color White/Black = 14 variants, inventory 5 each LOW STOCK, collections: New Arrivals)
    - Product 6: Graphic Print Tee (handle: graphic-print-tee, vendor: Acme Basics, type: T-Shirts, tags: ["new"], price: 2999, Options: Size S/M/L/XL = 4 variants, inventory 18 each, collections: T-Shirts)
    - Product 7: V-Neck Linen Tee (handle: v-neck-linen-tee, vendor: Acme Basics, type: T-Shirts, tags: ["popular"], price: 3499, Options: Size S/M/L x Color Beige/Olive/Sky Blue = 9 variants, inventory 12 each, collections: T-Shirts)
    - Product 8: Striped Polo Shirt (handle: striped-polo-shirt, vendor: Acme Basics, type: T-Shirts, tags: ["sale"], price: 2799, compare_at: 3999, Options: Size S/M/L/XL = 4 variants, inventory 10 each, collections: T-Shirts + Sale)
    - Product 9: Cargo Pants (handle: cargo-pants, vendor: Acme Workwear, type: Pants, tags: ["popular"], price: 5499, Options: Size 30/32/34/36 x Color Khaki/Olive/Black = 12 variants, inventory 14 each, collections: Pants & Jeans)
    - Product 10: Chino Shorts (handle: chino-shorts, vendor: Acme Basics, type: Pants, tags: ["new","trending"], price: 3999, Options: Size 30/32/34/36 x Color Navy/Sand = 8 variants, inventory 16 each, collections: Pants & Jeans + New Arrivals)
    - Product 11: Wide Leg Trousers (handle: wide-leg-trousers, vendor: Acme Denim, type: Pants, tags: ["sale"], price: 4999, compare_at: 6999, Options: Size S/M/L = 3 variants, inventory 7 each, collections: Pants & Jeans + Sale)
    - Product 12: Wool Scarf (handle: wool-scarf, vendor: Acme Accessories, type: Accessories, tags: ["popular"], price: 2999, Options: Color Grey/Burgundy/Navy = 3 variants, inventory 30 each)
    - Product 13: Canvas Tote Bag (handle: canvas-tote-bag, vendor: Acme Accessories, type: Accessories, tags: ["trending"], price: 1999, Options: Color Natural/Black = 2 variants, inventory 40 each)
    - Product 14: Bucket Hat (handle: bucket-hat, vendor: Acme Accessories, type: Accessories, tags: ["new","trending"], price: 2499, Options: Size S/M+L/XL x Color Beige/Black/Olive = 6 variants, inventory 22 each, collections: New Arrivals)
    - Product 15: DRAFT - Unreleased Winter Jacket (handle: unreleased-winter-jacket, status: draft, vendor: Acme Outerwear, type: Jackets, tags: ["limited"], price: 14999, published_at: null, Options: Size S/M/L/XL = 4 variants, inventory 0 each)
    - Product 16: ARCHIVED - Discontinued Raincoat (handle: discontinued-raincoat, status: archived, vendor: Acme Outerwear, type: Jackets, tags: [], price: 8999, published_at: 6 months ago, Options: Size M/L = 2 variants, inventory 3 each)
    - Product 17: Limited Edition Sneakers SOLD OUT (handle: limited-edition-sneakers, vendor: Acme Sport, type: Shoes, tags: ["limited"], price: 15999, Options: Size EU40/42/44 = 3 variants, inventory 0 each policy=deny)
    - Product 18: Backorder Denim Jacket OUT OF STOCK continue policy (handle: backorder-denim-jacket, vendor: Acme Denim, type: Jackets, tags: ["popular"], price: 9999, Options: Size S/M/L/XL = 4 variants, inventory 0 each policy=continue)
    - Product 19: Gift Card DIGITAL (handle: gift-card, vendor: Acme Fashion, type: Gift Cards, tags: ["popular"], SKUs: ACME-GIFT-25/50/100, prices: 2500/5000/10000, weight_g: 0, requires_shipping: 0, inventory 9999 each)
    - Product 20: Cashmere Overcoat EXPENSIVE (handle: cashmere-overcoat, vendor: Acme Premium, type: Jackets, tags: ["limited","new"], price: 49999, Options: Size S/M/L x Color Camel/Charcoal = 6 variants, inventory 3 each, collections: New Arrivals)

    Acme Electronics - 5 products:
    - E1: Pro Laptop 15, E2: Wireless Headphones, E3: USB-C Cable 2m, E4: Mechanical Keyboard, E5: Monitor Stand

11. **Collection-Product Assignments**: New Arrivals: 1,2,3,5,10,14,20; T-Shirts: 1,6,7,8; Pants & Jeans: 2,9,10,11; Sale: 2,8,11. Electronics: Featured: E1,E2,E4; Accessories: E3,E5.

12. **Discounts** (Acme Fashion): WELCOME10 (percent 10%, min_purchase_amount 2000, usage_count 3), FLAT5 (fixed 500), FREESHIP (free_shipping 0), EXPIRED20 (percent 20%, expired), MAXED (percent 10%, usage_limit 5, usage_count 5)

13. **Customers** (Acme Fashion 10): customer@acme.test/John Doe (with Home+Work addresses), jane@example.com/Jane Smith (with Home address), michael@example.com through anna@example.com with German addresses. Acme Electronics 2: techfan@example.com, gadgetlover@example.com.

14. **Orders** (Acme Fashion 15 orders #1001-#1015): Create with exact data as per spec including payments, fulfillments, refunds. Key orders:
    - #1001: John Doe, CC, paid/unfulfilled, 2x Classic Cotton T-Shirt S/White, total 5497
    - #1002: John Doe, CC, fulfilled/delivered, Organic Hoodie M + Classic Cotton T-Shirt L/Black, total 8997, fulfillment delivered
    - #1003: Jane Smith, CC, paid/partial fulfillment, Jeans 32/Blue + Belt L/XL/Brown, total 11997, only jeans shipped
    - #1004: John Doe, CC, cancelled/refunded, T-Shirt M/Navy, total 2998, full refund
    - #1005: Jane Smith, bank_transfer, pending/pending, Belt S/M/Black, total 3998
    - #1006-#1015: remaining orders per spec
    Acme Electronics 3 orders: #5001-#5003

15. **Themes**: Both stores with settings per spec (primary_color, hero_heading, etc.)

16. **Pages** (Acme Fashion 5): About Us, FAQ, Shipping & Returns, Privacy Policy, Terms of Service

17. **Navigation**: Acme Fashion: main-menu (Home, New Arrivals, T-Shirts, Pants & Jeans, Sale) + footer-menu (About Us, FAQ, Shipping & Returns, Privacy, Terms). Acme Electronics: main-menu (Home, Featured, Accessories).

18. **Analytics**: 30 days of analytics_daily data with growth trend, 220+ analytics_events over 7 days

19. **Search Settings**: synonyms and stop words per spec

For products, you must:
1. Create the product
2. Create product_options with position
3. Create product_option_values for each option
4. Create product_variants with proper SKU pattern
5. Insert into variant_option_values pivot to link each variant to its option values
6. Create inventory_items for each variant

To find the existing model names and table structures, look at:
- `app/Models/` for all models
- `database/migrations/` for table schemas
- `database/factories/` for factory patterns
- `app/Enums/` for enum values

Before writing code, read the relevant models, migrations, and factories to understand the exact field names and relationships. Pay close attention to:
- The `variant_option_values` pivot table structure
- The `collection_products` pivot table structure
- The `store_users` pivot table structure
- JSON field names (e.g., `settings_json`, `countries_json`, `config_json`)

The seeder should be split into individual seeder classes called from DatabaseSeeder in dependency order. Create these files:
- `database/seeders/DatabaseSeeder.php` (orchestrator)
- `database/seeders/OrganizationSeeder.php`
- `database/seeders/StoreSeeder.php`
- `database/seeders/UserSeeder.php`
- `database/seeders/ShippingSeeder.php`
- `database/seeders/CollectionSeeder.php`
- `database/seeders/ProductSeeder.php` (this is the biggest one)
- `database/seeders/DiscountSeeder.php`
- `database/seeders/CustomerSeeder.php`
- `database/seeders/OrderSeeder.php`
- `database/seeders/ThemeSeeder.php`
- `database/seeders/PageSeeder.php`
- `database/seeders/NavigationSeeder.php`
- `database/seeders/AnalyticsSeeder.php`
- `database/seeders/SearchSettingsSeeder.php`

After creating all seeders, run `php artisan migrate:fresh --seed` to verify everything works. Then run `php artisan test --compact` to make sure all existing tests still pass.

If there are test failures, fix the issues. Run `vendor/bin/pint --dirty --format agent` for code style.