prompt:
Read ALL the specification files in /Users/fabianwesner/Herd/shop/specs/ directory. I need the full content of:
1. specs/01-DATABASE-SCHEMA.md (read in chunks if needed)
2. specs/02-API-ROUTES.md (read in chunks if needed)
3. specs/03-ADMIN-UI.md (read in chunks if needed)
4. specs/04-STOREFRONT-UI.md (read in chunks if needed)
5. specs/05-BUSINESS-LOGIC.md (read in chunks if needed)
6. specs/06-AUTH-AND-SECURITY.md (read in chunks if needed)
7. specs/07-SEEDERS-AND-TEST-DATA.md (read in chunks if needed)
8. specs/08-PLAYWRIGHT-E2E-PLAN.md (read in chunks if needed)

For each file provide a comprehensive summary including:
- All table/column definitions
- All API endpoints
- All UI components and their specifications
- All business rules
- All test specifications
- All seeder data definitions

This is critical - I need ALL the details, not just summaries. Extract every table name, column, relationship, route, component, and test case.


userType: external

slug: iterative-seeking-pine

type: progress

type: assistant

type: message

role: assistant

service_tier: standard

prompt:
Read ALL the specification files in /Users/fabianwesner/Herd/shop/specs/ directory. I need the full content of:
1. specs/01-DATABASE-SCHEMA.md (read in chunks if needed)
2. specs/02-API-ROUTES.md (read in chunks if needed)
3. specs/03-ADMIN-UI.md (read in chunks if needed)
4. specs/04-STOREFRONT-UI.md (read in chunks if needed)
5. specs/05-BUSINESS-LOGIC.md (read in chunks if needed)
6. specs/06-AUTH-AND-SECURITY.md (read in chunks if needed)
7. specs/07-SEEDERS-AND-TEST-DATA.md (read in chunks if needed)
8. specs/08-PLAYWRIGHT-E2E-PLAN.md (read in chunks if needed)

For each file provide a comprehensive summary including:
- All table/column definitions
- All API endpoints
- All UI components and their specifications
- All business rules
- All test specifications
- All seeder data definitions

This is critical - I need ALL the details, not just summaries. Extract every table name, column, relationship, route, component, and test case.


type: user

role: user

content: <tool_use_error>Sibling tool call errored</tool_use_error>

toolUseResult: Sibling tool call errored

prompt:
Read ALL the specification files in /Users/fabianwesner/Herd/shop/specs/ directory. I need the full content of:
1. specs/01-DATABASE-SCHEMA.md (read in chunks if needed)
2. specs/02-API-ROUTES.md (read in chunks if needed)
3. specs/03-ADMIN-UI.md (read in chunks if needed)
4. specs/04-STOREFRONT-UI.md (read in chunks if needed)
5. specs/05-BUSINESS-LOGIC.md (read in chunks if needed)
6. specs/06-AUTH-AND-SECURITY.md (read in chunks if needed)
7. specs/07-SEEDERS-AND-TEST-DATA.md (read in chunks if needed)
8. specs/08-PLAYWRIGHT-E2E-PLAN.md (read in chunks if needed)

For each file provide a comprehensive summary including:
- All table/column definitions
- All API endpoints
- All UI components and their specifications
- All business rules
- All test specifications
- All seeder data definitions

This is critical - I need ALL the details, not just summaries. Extract every table name, column, relationship, route, component, and test case.


prompt:
Read ALL the specification files in /Users/fabianwesner/Herd/shop/specs/ directory. I need the full content of:
1. specs/01-DATABASE-SCHEMA.md (read in chunks if needed)
2. specs/02-API-ROUTES.md (read in chunks if needed)
3. specs/03-ADMIN-UI.md (read in chunks if needed)
4. specs/04-STOREFRONT-UI.md (read in chunks if needed)
5. specs/05-BUSINESS-LOGIC.md (read in chunks if needed)
6. specs/06-AUTH-AND-SECURITY.md (read in chunks if needed)
7. specs/07-SEEDERS-AND-TEST-DATA.md (read in chunks if needed)
8. specs/08-PLAYWRIGHT-E2E-PLAN.md (read in chunks if needed)

For each file provide a comprehensive summary including:
- All table/column definitions
- All API endpoints
- All UI components and their specifications
- All business rules
- All test specifications
- All seeder data definitions

This is critical - I need ALL the details, not just summaries. Extract every table name, column, relationship, route, component, and test case.


prompt:
Read ALL the specification files in /Users/fabianwesner/Herd/shop/specs/ directory. I need the full content of:
1. specs/01-DATABASE-SCHEMA.md (read in chunks if needed)
2. specs/02-API-ROUTES.md (read in chunks if needed)
3. specs/03-ADMIN-UI.md (read in chunks if needed)
4. specs/04-STOREFRONT-UI.md (read in chunks if needed)
5. specs/05-BUSINESS-LOGIC.md (read in chunks if needed)
6. specs/06-AUTH-AND-SECURITY.md (read in chunks if needed)
7. specs/07-SEEDERS-AND-TEST-DATA.md (read in chunks if needed)
8. specs/08-PLAYWRIGHT-E2E-PLAN.md (read in chunks if needed)

For each file provide a comprehensive summary including:
- All table/column definitions
- All API endpoints
- All UI components and their specifications
- All business rules
- All test specifications
- All seeder data definitions

This is critical - I need ALL the details, not just summaries. Extract every table name, column, relationship, route, component, and test case.


Bash

command: find /Users/fabianwesner/Herd/shop/resources/views -type f -name "*.blade.php" | sort

description: List all blade view files

prompt:
Explore the current state of the Laravel project at /Users/fabianwesner/Herd/shop. I need to understand:

1. What files already exist (models, controllers, migrations, routes, views, tests, etc.)
2. Current state of composer.json dependencies
3. Current state of package.json
4. Current .env configuration
5. Current config files (auth.php, database.php, etc.)
6. Current routes/web.php, routes/api.php, routes/console.php
7. Current app directory structure
8. Current tests directory structure
9. Current database/migrations directory
10. Current resources/views directory

Give me a comprehensive overview of what already exists vs what needs to be built.


content:
/Users/fabianwesner/Herd/shop/resources/views/components/action-message.blade.php
/Users/fabianwesner/Herd/shop/resources/views/components/app-logo-icon.blade.php
/Users/fabianwesner/Herd/shop/resources/views/components/app-logo.blade.php
/Users/fabianwesner/Herd/shop/resources/views/components/auth-header.blade.php
/Users/fabianwesner/Herd/shop/resources/views/components/auth-session-status.blade.php
/Users/fabianwesner/Herd/shop/resources/views/components/desktop-user-menu.blade.php
/Users/fabianwesner/Herd/shop/resources/views/components/placeholder-pattern.blade.php
/Users/fabianwesner/Herd/shop/resources/views/components/settings/layout.blade.php
/Users/fabianwesner/Herd/shop/resources/views/dashboard.blade.php
/Users/fabianwesner/Herd/shop/resources/views/flux/icon/book-open-text.blade.php
/Users/fabianwesner/Herd/shop/resources/views/flux/icon/chevrons-up-down.blade.php
/Users/fabianwesner/Herd/shop/resources/views/flux/icon/folder-git-2.blade.php
/Users/fabianwesner/Herd/shop/resources/views/flux/icon/layout-grid.blade.php
/Users/fabianwesner/Herd/shop/resources/views/flux/navlist/group.blade.php
/Users/fabianwesner/Herd/shop/resources/views/layouts/app.blade.php
/Users/fabianwesner/Herd/shop/resources/views/layouts/app/header.blade.php
/Users/fabianwesner/Herd/shop/resources/views/layouts/app/sidebar.blade.php
/Users/fabianwesner/Herd/shop/resources/views/layouts/auth.blade.php
/Users/fabianwesner/Herd/shop/resources/views/layouts/auth/card.blade.php
/Users/fabianwesner/Herd/shop/resources/views/layouts/auth/simple.blade.php
/Users/fabianwesner/Herd/shop/resources/views/layouts/auth/split.blade.php
/Users/fabianwesner/Herd/shop/resources/views/livewire/auth/confirm-password.blade.php
/Users/fabianwesner/Herd/shop/resources/views/livewire/auth/forgot-password.blade.php
/Users/fabianwesner/Herd/shop/resources/views/livewire/auth/login.blade.php
/Users/fabianwesner/Herd/shop/resources/views/livewire/auth/register.blade.php
/Users/fabianwesner/Herd/shop/resources/views/livewire/auth/reset-password.blade.php
/Users/fabianwesner/Herd/shop/resources/views/livewire/auth/two-factor-challenge.blade.php
/Users/fabianwesner/Herd/shop/resources/views/livewire/auth/verify-email.blade.php
/Users/fabianwesner/Herd/shop/resources/views/livewire/settings/appearance.blade.php
/Users/fabianwesner/Herd/shop/resources/views/livewire/settings/delete-user-form.blade.php
/Users/fabianwesner/Herd/shop/resources/views/livewire/settings/password.blade.php
/Users/fabianwesner/Herd/shop/resources/views/livewire/settings/profile.blade.php
/Users/fabianwesner/Herd/shop/resources/views/livewire/settings/two-factor.blade.php
/Users/fabianwesner/Herd/shop/resources/views/livewire/settings/two-factor/recovery-codes.blade.php
/Users/fabianwesner/Herd/shop/resources/views/partials/head.blade.php
/Users/fabianwesner/Herd/shop/resources/views/partials/settings-heading.blade.php
/Users/fabianwesner/Herd/shop/resources/views/welcome.blade.php

prompt:
Explore the current state of the Laravel project at /Users/fabianwesner/Herd/shop. I need to understand:

1. What files already exist (models, controllers, migrations, routes, views, tests, etc.)
2. Current state of composer.json dependencies
3. Current state of package.json
4. Current .env configuration
5. Current config files (auth.php, database.php, etc.)
6. Current routes/web.php, routes/api.php, routes/console.php
7. Current app directory structure
8. Current tests directory structure
9. Current database/migrations directory
10. Current resources/views directory

Give me a comprehensive overview of what already exists vs what needs to be built.


prompt:
Explore the current state of the Laravel project at /Users/fabianwesner/Herd/shop. I need to understand:

1. What files already exist (models, controllers, migrations, routes, views, tests, etc.)
2. Current state of composer.json dependencies
3. Current state of package.json
4. Current .env configuration
5. Current config files (auth.php, database.php, etc.)
6. Current routes/web.php, routes/api.php, routes/console.php
7. Current app directory structure
8. Current tests directory structure
9. Current database/migrations directory
10. Current resources/views directory

Give me a comprehensive overview of what already exists vs what needs to be built.


content:
     1→<?php
     2→
     3→use Illuminate\Support\Facades\Route;
     4→
     5→Route::get('/', function () {
     6→    return view('welcome');
     7→})->name('home');
     8→
     9→Route::view('dashboard', 'dashboard')
    10→    ->middleware(['auth', 'verified'])
    11→    ->name('dashboard');
    12→
    13→require __DIR__.'/settings.php';
    14→

prompt:
Explore the current state of the Laravel project at /Users/fabianwesner/Herd/shop. I need to understand:

1. What files already exist (models, controllers, migrations, routes, views, tests, etc.)
2. Current state of composer.json dependencies
3. Current state of package.json
4. Current .env configuration
5. Current config files (auth.php, database.php, etc.)
6. Current routes/web.php, routes/api.php, routes/console.php
7. Current app directory structure
8. Current tests directory structure
9. Current database/migrations directory
10. Current resources/views directory

Give me a comprehensive overview of what already exists vs what needs to be built.


prompt:
Explore the current state of the Laravel project at /Users/fabianwesner/Herd/shop. I need to understand:

1. What files already exist (models, controllers, migrations, routes, views, tests, etc.)
2. Current state of composer.json dependencies
3. Current state of package.json
4. Current .env configuration
5. Current config files (auth.php, database.php, etc.)
6. Current routes/web.php, routes/api.php, routes/console.php
7. Current app directory structure
8. Current tests directory structure
9. Current database/migrations directory
10. Current resources/views directory

Give me a comprehensive overview of what already exists vs what needs to be built.


content:
     1→<?php
     2→
     3→use Illuminate\Foundation\Inspiring;
     4→use Illuminate\Support\Facades\Artisan;
     5→
     6→Artisan::command('inspire', function () {
     7→    $this->comment(Inspiring::quote());
     8→})->purpose('Display an inspiring quote');
     9→

prompt:
Explore the current state of the Laravel project at /Users/fabianwesner/Herd/shop. I need to understand:

1. What files already exist (models, controllers, migrations, routes, views, tests, etc.)
2. Current state of composer.json dependencies
3. Current state of package.json
4. Current .env configuration
5. Current config files (auth.php, database.php, etc.)
6. Current routes/web.php, routes/api.php, routes/console.php
7. Current app directory structure
8. Current tests directory structure
9. Current database/migrations directory
10. Current resources/views directory

Give me a comprehensive overview of what already exists vs what needs to be built.


prompt:
Explore the current state of the Laravel project at /Users/fabianwesner/Herd/shop. I need to understand:

1. What files already exist (models, controllers, migrations, routes, views, tests, etc.)
2. Current state of composer.json dependencies
3. Current state of package.json
4. Current .env configuration
5. Current config files (auth.php, database.php, etc.)
6. Current routes/web.php, routes/api.php, routes/console.php
7. Current app directory structure
8. Current tests directory structure
9. Current database/migrations directory
10. Current resources/views directory

Give me a comprehensive overview of what already exists vs what needs to be built.


content:
     1→<?php
     2→
     3→use App\Livewire\Settings\Appearance;
     4→use App\Livewire\Settings\Password;
     5→use App\Livewire\Settings\Profile;
     6→use App\Livewire\Settings\TwoFactor;
     7→use Illuminate\Support\Facades\Route;
     8→use Laravel\Fortify\Features;
     9→
    10→Route::middleware(['auth'])->group(function () {
    11→    Route::redirect('settings', 'settings/profile');
    12→
    13→    Route::livewire('settings/profile', Profile::class)->name('profile.edit');
    14→});
    15→
    16→Route::middleware(['auth', 'verified'])->group(function () {
    17→    Route::livewire('settings/password', Password::class)->name('user-password.edit');
    18→    Route::livewire('settings/appearance', Appearance::class)->name('appearance.edit');
    19→
    20→    Route::livewire('settings/two-factor', TwoFactor::class)
    21→        ->middleware(
    22→            when(
    23→                Features::canManageTwoFactorAuthentication()
    24→                && Features::optionEnabled(Features::twoFactorAuthentication(), 'confirmPassword'),
    25→                ['password.confirm'],
    26→                [],
    27→            ),
    28→        )
    29→        ->name('two-factor.show');
    30→});
    31→

prompt:
Explore the current state of the Laravel project at /Users/fabianwesner/Herd/shop. I need to understand:

1. What files already exist (models, controllers, migrations, routes, views, tests, etc.)
2. Current state of composer.json dependencies
3. Current state of package.json
4. Current .env configuration
5. Current config files (auth.php, database.php, etc.)
6. Current routes/web.php, routes/api.php, routes/console.php
7. Current app directory structure
8. Current tests directory structure
9. Current database/migrations directory
10. Current resources/views directory

Give me a comprehensive overview of what already exists vs what needs to be built.


prompt:
Read ALL the specification files in /Users/fabianwesner/Herd/shop/specs/ directory. I need the full content of:
1. specs/01-DATABASE-SCHEMA.md (read in chunks if needed)
2. specs/02-API-ROUTES.md (read in chunks if needed)
3. specs/03-ADMIN-UI.md (read in chunks if needed)
4. specs/04-STOREFRONT-UI.md (read in chunks if needed)
5. specs/05-BUSINESS-LOGIC.md (read in chunks if needed)
6. specs/06-AUTH-AND-SECURITY.md (read in chunks if needed)
7. specs/07-SEEDERS-AND-TEST-DATA.md (read in chunks if needed)
8. specs/08-PLAYWRIGHT-E2E-PLAN.md (read in chunks if needed)

For each file provide a comprehensive summary including:
- All table/column definitions
- All API endpoints
- All UI components and their specifications
- All business rules
- All test specifications
- All seeder data definitions

This is critical - I need ALL the details, not just summaries. Extract every table name, column, relationship, route, component, and test case.


content:
     1→# API and Routing Specification
     2→
     3→This document defines every route, endpoint, middleware stack, request/response schema, and rate limit for the e-commerce platform. The admin panel is built with Livewire v4 + Flux UI (server-rendered), while the storefront uses Blade + Livewire. REST API endpoints serve cart/checkout operations and programmatic admin access.
     4→
     5→All monetary amounts are stored and transmitted in **minor units** (cents). All IDs are auto-incrementing integers. All monetary amounts are in minor units (cents).
     6→
     7→---
     8→
     9→## 1. Web Routes (Blade/Livewire Pages)
    10→
    11→### 1.1 Admin Auth Routes
    12→
    13→No middleware beyond the default `web` group. These routes handle admin login and logout.
    14→
    15→| Method | Path | Description | Component / Action |
    16→|--------|------|-------------|--------------------|
    17→| GET | `/admin/login` | Admin login page | `Livewire: Admin\Auth\Login` |
    18→| POST | `/admin/login` | Process admin login | Handled by Livewire action |
    19→| POST | `/admin/logout` | Logout and invalidate session | Handled by Livewire action |
    20→| GET | `/admin/forgot-password` | Show forgot password form | `Livewire: Admin\Auth\ForgotPassword` |
    21→| POST | `/admin/forgot-password` | Send password reset link | Handled by Livewire action |
    22→| GET | `/admin/reset-password/{token}` | Show reset password form | `Livewire: Admin\Auth\ResetPassword` |
    23→| POST | `/admin/reset-password` | Reset password with token | Handled by Livewire action |
    24→
    25→Rate limit: `login` tier (5 per minute per IP).
    26→
    27→---
    28→
    29→### 1.2 Admin Routes
    30→
    31→**Prefix:** `/admin`
    32→**Middleware:** `web`, `auth`, `verified`, `store.resolve`, `role.check`
    33→
    34→All admin pages render Livewire full-page components. Data mutations happen via Livewire actions (not separate POST endpoints).
    35→
    36→> **Note:** The Create and Edit routes render the same Livewire form component (`ProductForm`, `OrderForm`, etc.) in create vs edit mode, following the shared-form pattern defined in Spec 03.
    37→
    38→#### Dashboard
    39→
    40→| Method | Path | Description | Component |
    41→|--------|------|-------------|-----------|
    42→| GET | `/admin` | Dashboard with KPI tiles, orders chart, conversion funnel | `Admin\Dashboard` |
    43→
    44→#### Products
    45→
    46→| Method | Path | Description | Component |
    47→|--------|------|-------------|-----------|
    48→| GET | `/admin/products` | Product list - searchable, filterable by status, bulk actions | `Admin\Products\Index` |
    49→| GET | `/admin/products/create` | Create product form | `Admin\Products\Create` |
    50→| GET | `/admin/products/{product}/edit` | Edit product form with variants, media, options | `Admin\Products\Edit` |
    51→
    52→#### Inventory
    53→
    54→| Method | Path | Description | Component |
    55→|--------|------|-------------|-----------|
    56→| GET | `/admin/inventory` | Inventory management page | `Admin\Inventory\Index` |
    57→
    58→#### Collections
    59→
    60→| Method | Path | Description | Component |
    61→|--------|------|-------------|-----------|
    62→| GET | `/admin/collections` | Collection list | `Admin\Collections\Index` |
    63→| GET | `/admin/collections/create` | Create collection form | `Admin\Collections\Create` |
    64→| GET | `/admin/collections/{collection}/edit` | Edit collection form | `Admin\Collections\Edit` |
    65→
    66→#### Orders
    67→
    68→| Method | Path | Description | Component |
    69→|--------|------|-------------|-----------|
    70→| GET | `/admin/orders` | Order list with status filters | `Admin\Orders\Index` |
    71→| GET | `/admin/orders/{order}` | Order detail with timeline, payments, fulfillments | `Admin\Orders\Show` |
    72→
    73→#### Customers
    74→
    75→| Method | Path | Description | Component |
    76→|--------|------|-------------|-----------|
    77→| GET | `/admin/customers` | Customer list | `Admin\Customers\Index` |
    78→| GET | `/admin/customers/{customer}` | Customer detail with order history, addresses | `Admin\Customers\Show` |
    79→
    80→#### Discounts
    81→
    82→| Method | Path | Description | Component |
    83→|--------|------|-------------|-----------|
    84→| GET | `/admin/discounts` | Discount list | `Admin\Discounts\Index` |
    85→| GET | `/admin/discounts/create` | Create discount form with rules builder | `Admin\Discounts\Create` |
    86→| GET | `/admin/discounts/{discount}/edit` | Edit discount form | `Admin\Discounts\Edit` |
    87→
    88→#### Settings
    89→
    90→| Method | Path | Description | Component |
    91→|--------|------|-------------|-----------|
    92→| GET | `/admin/settings` | Store settings (tabs: General, Domains, Shipping, Taxes, Checkout, Notifications) | `Admin\Settings\Index` |
    93→| GET | `/admin/settings/shipping` | Shipping zones and rates configuration | `Admin\Settings\Shipping` |
    94→| GET | `/admin/settings/taxes` | Tax settings - mode, provider config, rates | `Admin\Settings\Taxes` |
    95→
    96→#### Themes and Online Store
    97→
    98→| Method | Path | Description | Component |
    99→|--------|------|-------------|-----------|
   100→| GET | `/admin/themes` | Theme management - cards with actions (Customize, Preview, Publish, Duplicate, Delete) | `Admin\Themes\Index` |
   101→| GET | `/admin/themes/{theme}/editor` | Theme editor - left panel sections, center preview iframe, right panel settings | `Admin\Themes\Editor` |
   102→
   103→#### Pages
   104→
   105→| Method | Path | Description | Component |
   106→|--------|------|-------------|-----------|
   107→| GET | `/admin/pages` | Content pages list | `Admin\Pages\Index` |
   108→| GET | `/admin/pages/create` | Create content page | `Admin\Pages\Create` |
   109→| GET | `/admin/pages/{page}/edit` | Edit content page | `Admin\Pages\Edit` |
   110→
   111→#### Navigation
   112→
   113→| Method | Path | Description | Component |
   114→|--------|------|-------------|-----------|
   115→| GET | `/admin/navigation` | Navigation menus management | `Admin\Navigation\Index` |
   116→
   117→#### Apps
   118→
   119→| Method | Path | Description | Component |
   120→|--------|------|-------------|-----------|
   121→| GET | `/admin/apps` | Apps directory - list installed and available apps | `Admin\Apps\Index` |
   122→| GET | `/admin/apps/{installation}` | Installed app detail - scopes, webhooks, usage | `Admin\Apps\Show` |
   123→
   124→#### Developers
   125→
   126→| Method | Path | Description | Component |
   127→|--------|------|-------------|-----------|
   128→| GET | `/admin/developers` | Developer settings - API keys/tokens, webhook management | `Admin\Developers\Index` |
   129→
   130→> **Note:** Webhook subscription management (CRUD) is available exclusively through the admin UI at `/admin/developers`. There are no REST API endpoints for programmatic webhook subscription management in the initial implementation.
   131→
   132→#### Analytics
   133→
   134→| Method | Path | Description | Component |
   135→|--------|------|-------------|-----------|
   136→| GET | `/admin/analytics` | Analytics dashboard - sales, traffic, funnel, exports | `Admin\Analytics\Index` |
   137→
   138→#### Search Settings
   139→
   140→| Method | Path | Description | Component |
   141→|--------|------|-------------|-----------|
   142→| GET | `/admin/search/settings` | Search settings - synonyms, stopwords, reindex | `Admin\Search\Settings` |
   143→
   144→---
   145→
   146→### 1.3 Storefront Routes
   147→
   148→**Middleware:** `web`, `store.resolve`
   149→
   150→These routes serve the public-facing storefront using Blade templates rendered through the active theme engine.
   151→
   152→| Method | Path | Description | Component / View |
   153→|--------|------|-------------|------------------|
   154→| GET | `/` | Home page | `Storefront\Home` |
   155→| GET | `/collections` | All collections listing | `Storefront\Collections\Index` |
   156→| GET | `/collections/{handle}` | Single collection page with product grid and filters | `Storefront\Collections\Show` |
   157→| GET | `/products/{handle}` | Product page - gallery, variant selector, add-to-cart | `Storefront\Products\Show` |
   158→| GET | `/cart` | Full cart page - line items, quantity controls, discount input, totals | `Storefront\Cart\Show` |
   159→| GET | `/search` | Search results page with filter panel, sort dropdown, product grid | `Storefront\Search\Index` |
   160→| GET | `/pages/{handle}` | Static content page (About, FAQ, etc.) | `Storefront\Pages\Show` |
   161→
   162→#### Customer Account Routes
   163→
   164→**Additional middleware:** `auth:customer`
   165→
   166→| Method | Path | Description | Component |
   167→|--------|------|-------------|-----------|
   168→| GET | `/account` | Customer account overview | `Storefront\Account\Dashboard` |
   169→| GET | `/account/orders` | Order history list | `Storefront\Account\Orders\Index` |
   170→| GET | `/account/orders/{orderNumber}` | Order detail | `Storefront\Account\Orders\Show` |
   171→| GET | `/account/addresses` | Address book management | `Storefront\Account\Addresses\Index` |
   172→
   173→#### Customer Auth Routes (no auth required)
   174→
   175→| Method | Path | Description | Component |
   176→|--------|------|-------------|-----------|
   177→| GET | `/account/login` | Customer login page | `Storefront\Account\Auth\Login` |
   178→| GET | `/account/register` | Customer registration page | `Storefront\Account\Auth\Register` |
   179→| GET | `/forgot-password` | Forgot password page | `Storefront\Account\Auth\ForgotPassword` |
   180→| POST | `/forgot-password` | Send reset link | Handled by Livewire action |
   181→| GET | `/reset-password/{token}` | Reset password form | `Storefront\Account\Auth\ResetPassword` |
   182→| POST | `/reset-password` | Reset password | Handled by Livewire action |
   183→
   184→---
   185→
   186→### 1.4 Checkout Routes
   187→
   188→**Middleware:** `web`, `store.resolve`
   189→
   190→| Method | Path | Description | Component |
   191→|--------|------|-------------|-----------|
   192→| GET | `/checkout/{checkoutId}` | Checkout page - stepper UI (Contact -> Shipping -> Payment) | `Storefront\Checkout\Show` |
   193→| GET | `/checkout/{checkoutId}/confirmation` | Order confirmation page - order number, items, totals | `Storefront\Checkout\Confirmation` |
   194→
   195→---
   196→
   197→## 2. Storefront REST API
   198→
   199→**Prefix:** `/api/storefront/v1`
   200→**Middleware:** `store.resolve`, `throttle:api.storefront`
   201→**Content-Type:** `application/json`
   202→
   203→These endpoints power the interactive storefront experience (cart drawer, checkout flow, search autocomplete, analytics).
   204→
   205→---
   206→
   207→### 2.1 Cart Endpoints
   208→
   209→#### POST /api/storefront/v1/carts
   210→
   211→Create a new cart.
   212→
   213→- **Authentication:** None (guest) or session-based (logged-in customer)
   214→- **Rate limit:** `api.storefront` (120/min per IP)
   215→
   216→**Request body:**
   217→
   218→```json
   219→{
   220→  "currency": "EUR"
   221→}
   222→```
   223→
   224→| Field | Type | Required | Validation |
   225→|-------|------|----------|------------|
   226→| `currency` | string | No | Valid ISO 4217 code. Defaults to store's `default_currency`. |
   227→
   228→**Success response:** `201 Created`
   229→
   230→```json
   231→{
   232→  "id": 42,
   233→  "store_id": 1,
   234→  "customer_id": null,
   235→  "currency": "EUR",
   236→  "cart_version": 1,
   237→  "status": "active",
   238→  "lines": [],
   239→  "totals": {
   240→    "subtotal": 0,
   241→    "discount": 0,
   242→    "total": 0,
   243→    "currency": "EUR"
   244→  },
   245→  "created_at": "2026-02-11T10:00:00Z",
   246→  "updated_at": "2026-02-11T10:00:00Z"
   247→}
   248→```
   249→
   250→**Error responses:**
   251→- `422` - Validation error (invalid currency)
   252→
   253→---
   254→
   255→#### GET /api/storefront/v1/carts/{cartId}
   256→
   257→Retrieve a cart with all lines and computed totals.
   258→
   259→- **Authentication:** None (cart ID acts as token)
   260→- **Rate limit:** `api.storefront` (120/min per IP)
   261→
   262→**URL parameters:**
   263→
   264→| Parameter | Type | Required | Description |
   265→|-----------|------|----------|-------------|
   266→| `cartId` | integer | Yes | The cart identifier |
   267→
   268→**Success response:** `200 OK`
   269→
   270→```json
   271→{
   272→  "id": 42,
   273→  "store_id": 1,
   274→  "customer_id": null,
   275→  "currency": "EUR",
   276→  "cart_version": 3,
   277→  "status": "active",
   278→  "lines": [
   279→    {
   280→      "id": 201,
   281→      "variant_id": 101,
   282→      "product_title": "Classic T-Shirt",
   283→      "variant_title": "Blue / Medium",
   284→      "sku": "TSH-BLU-M",
   285→      "quantity": 2,
   286→      "unit_price_amount": 2500,
   287→      "line_subtotal_amount": 5000,
   288→      "line_discount_amount": 0,
   289→      "line_total_amount": 5000,
   290→      "image_url": "https://cdn.example.com/products/tshirt-blue.jpg",
   291→      "requires_shipping": true,
   292→      "available_quantity": 50
   293→    }
   294→  ],
   295→  "totals": {
   296→    "subtotal": 5000,
   297→    "discount": 0,
   298→    "total": 5000,
   299→    "currency": "EUR",
   300→    "line_count": 1,
   301→    "item_count": 2
   302→  },
   303→  "created_at": "2026-02-11T10:00:00Z",
   304→  "updated_at": "2026-02-11T10:05:00Z"
   305→}
   306→```
   307→
   308→**Error responses:**
   309→- `404` - Cart not found
   310→
   311→---
   312→
   313→#### POST /api/storefront/v1/carts/{cartId}/lines
   314→
   315→Add a line item to the cart.
   316→
   317→- **Authentication:** None
   318→- **Rate limit:** `api.storefront` (120/min per IP)
   319→
   320→**Request body:**
   321→
   322→```json
   323→{
   324→  "variant_id": 101,
   325→  "quantity": 2
   326→}
   327→```
   328→
   329→| Field | Type | Required | Validation |
   330→|-------|------|----------|------------|
   331→| `variant_id` | integer | Yes | Must exist, belong to an active product in the current store |
   332→| `quantity` | integer | Yes | Min: 1, max: 9999. Must not exceed available inventory when policy is `deny`. |
   333→
   334→**Success response:** `201 Created`
   335→
   336→Returns the full cart object (same schema as GET /carts/{cartId}).
   337→
   338→**Error responses:**
   339→- `404` - Cart not found
   340→- `409` - Cart version conflict (concurrent modification, retry with fresh cart_version)
   341→- `422` - Validation error
   342→
   343→```json
   344→{
   345→  "message": "The given data was invalid.",
   346→  "errors": {
   347→    "variant_id": ["The selected variant is out of stock."]
   348→  }
   349→}
   350→```
   351→
   352→---
   353→
   354→#### PUT /api/storefront/v1/carts/{cartId}/lines/{lineId}
   355→
   356→Update the quantity of a cart line.
   357→
   358→- **Authentication:** None
   359→- **Rate limit:** `api.storefront` (120/min per IP)
   360→
   361→**Request body:**
   362→
   363→```json
   364→{
   365→  "quantity": 3,
   366→  "cart_version": 3
   367→}
   368→```
   369→
   370→| Field | Type | Required | Validation |
   371→|-------|------|----------|------------|
   372→| `quantity` | integer | Yes | Min: 1, max: 9999. Must not exceed available inventory when policy is `deny`. |
   373→| `cart_version` | integer | Yes | Must match current cart version for optimistic concurrency. |
   374→
   375→**Success response:** `200 OK`
   376→
   377→Returns the full cart object with updated totals.
   378→
   379→**Error responses:**
   380→- `404` - Cart or line not found
   381→- `409` - Version conflict
   382→- `422` - Validation error (e.g., exceeds inventory)
   383→
   384→---
   385→
   386→#### DELETE /api/storefront/v1/carts/{cartId}/lines/{lineId}
   387→
   388→Remove a line item from the cart.
   389→
   390→- **Authentication:** None
   391→- **Rate limit:** `api.storefront` (120/min per IP)
   392→
   393→**Request body:**
   394→
   395→```json
   396→{
   397→  "cart_version": 3
   398→}
   399→```
   400→
   401→| Field | Type | Required | Validation |
   402→|-------|------|----------|------------|
   403→| `cart_version` | integer | Yes | Must match current cart version. |
   404→
   405→**Success response:** `200 OK`
   406→
   407→Returns the full cart object.
   408→
   409→**Error responses:**
   410→- `404` - Cart or line not found
   411→- `409` - Version conflict
   412→
   413→---
   414→
   415→### 2.2 Checkout Endpoints
   416→
   417→#### Checkout Flow Sequence Diagram
   418→
   419→```mermaid
   420→sequenceDiagram
   421→    participant C as Customer Browser
   422→    participant API as Storefront API
   423→    participant MockPSP as Mock Payment Provider
   424→
   425→    C->>API: POST /checkouts (cart_id, email)
   426→    API-->>C: 201 Checkout (status: started)
   427→
   428→    C->>API: PUT /checkouts/{id}/address
   429→    API-->>C: 200 Checkout (status: addressed, available_shipping_methods)
   430→
   431→    C->>API: PUT /checkouts/{id}/shipping-method
   432→    API-->>C: 200 Checkout (status: shipping_selected, totals with tax)
   433→
   434→    opt Apply discount
   435→        C->>API: POST /checkouts/{id}/apply-discount
   436→        API-->>C: 200 Checkout (totals recalculated)
   437→    end
   438→
   439→    C->>API: POST /checkouts/{id}/pay (payment_method + details)
   440→    API->>API: Reserve inventory
   441→    API->>MockPSP: charge(checkout, paymentMethodData)
   442→
   443→    alt Credit Card / PayPal (instant capture)
   444→        MockPSP-->>API: PaymentResult (success)
   445→        API->>API: Create order (paid), commit inventory
   446→        API-->>C: 200 (order confirmation data)
   447→    else Credit Card (decline)
   448→        MockPSP-->>API: PaymentResult (failed)
   449→        API->>API: Release reserved inventory
   450→        API-->>C: 422 (error: card_declined / insufficient_funds)
   451→    else Bank Transfer (deferred)
   452→        MockPSP-->>API: PaymentResult (pending)
   453→        API->>API: Create order (pending), inventory stays reserved
   454→        API-->>C: 200 (order confirmation + bank transfer instructions)
   455→    end
   456→```
   457→
   458→#### POST /api/storefront/v1/checkouts
   459→
   460→Create a checkout from a cart.
   461→
   462→- **Authentication:** None or session-based
   463→- **Rate limit:** `checkout` (10/min per session)
   464→
   465→**Request body:**
   466→
   467→```json
   468→{
   469→  "cart_id": 42,
   470→  "email": "customer@example.com"
   471→}
   472→```
   473→
   474→| Field | Type | Required | Validation |
   475→|-------|------|----------|------------|
   476→| `cart_id` | integer | Yes | Must reference an active cart with at least one line. |
   477→| `email` | string | Yes | Valid email address. |
   478→
   479→**Success response:** `201 Created`
   480→
   481→```json
   482→{
   483→  "id": 7,
   484→  "store_id": 1,
   485→  "cart_id": 42,
   486→  "customer_id": null,
   487→  "status": "started",
   488→  "email": "customer@example.com",
   489→  "shipping_address_json": null,
   490→  "billing_address_json": null,
   491→  "shipping_method_id": null,
   492→  "discount_code": null,
   493→  "lines": [
   494→    {
   495→      "variant_id": 101,
   496→      "product_title": "Classic T-Shirt",
   497→      "variant_title": "Blue / Medium",
   498→      "sku": "TSH-BLU-M",
   499→      "quantity": 2,
   500→      "unit_price_amount": 2500,
   501→      "line_total_amount": 5000
   502→    }
   503→  ],
   504→  "totals": {
   505→    "subtotal": 5000,
   506→    "discount": 0,
   507→    "shipping": 0,
   508→    "tax": 0,
   509→    "total": 5000,
   510→    "currency": "EUR"
   511→  },
   512→  "available_shipping_methods": [],
   513→  "expires_at": "2026-02-12T10:00:00Z",
   514→  "created_at": "2026-02-11T10:00:00Z"
   515→}
   516→```
   517→
   518→**Error responses:**
   519→- `404` - Cart not found
   520→- `422` - Validation errors (empty cart, invalid email)
   521→
   522→---
   523→
   524→#### GET /api/storefront/v1/checkouts/{checkoutId}
   525→
   526→Retrieve the current checkout state and computed totals.
   527→
   528→- **Authentication:** None (checkout ID acts as token)
   529→- **Rate limit:** `checkout` (10/min per session)
   530→
   531→**Success response:** `200 OK`
   532→
   533→Returns the full checkout object (same schema as POST response, with updated fields reflecting the current state).
   534→
   535→**Error responses:**
   536→- `404` - Checkout not found
   537→- `410` - Checkout expired
   538→
   539→---
   540→
   541→#### PUT /api/storefront/v1/checkouts/{checkoutId}/address
   542→
   543→Set the shipping and/or billing address on a checkout.
   544→
   545→- **Authentication:** None
   546→- **Rate limit:** `checkout` (10/min per session)
   547→
   548→**Request body:**
   549→
   550→```json
   551→{
   552→  "shipping_address": {
   553→    "first_name": "Jane",
   554→    "last_name": "Doe",
   555→    "address1": "123 Main St",
   556→    "address2": "Apt 4B",
   557→    "city": "Berlin",
   558→    "province": "Berlin",
   559→    "province_code": "BE",
   560→    "country": "DE",
   561→    "country_code": "DE",
   562→    "postal_code": "10115",
   563→    "phone": "+49301234567"
   564→  },
   565→  "billing_address": null,
   566→  "use_shipping_as_billing": true
   567→}
   568→```
   569→
   570→**Shipping address fields:**
   571→
   572→| Field | Type | Required | Validation |
   573→|-------|------|----------|------------|
   574→| `shipping_address` | object | Yes (if any line requires shipping) | See fields below |
   575→| `shipping_address.first_name` | string | Yes | Max: 255 |
   576→| `shipping_address.last_name` | string | Yes | Max: 255 |
   577→| `shipping_address.address1` | string | Yes | Max: 500 |
   578→| `shipping_address.address2` | string | No | Max: 500 |
   579→| `shipping_address.city` | string | Yes | Max: 255 |
   580→| `shipping_address.province` | string | No | Max: 255 |
   581→| `shipping_address.province_code` | string | No | Max: 10 |
   582→| `shipping_address.country` | string | Yes | Max: 255 |
   583→| `shipping_address.country_code` | string | Yes | Valid ISO 3166-1 alpha-2 |
   584→| `shipping_address.postal_code` | string | Yes | Max: 20 |
   585→| `shipping_address.phone` | string | No | Max: 50 |
   586→| `billing_address` | object | No | Same fields as shipping_address |
   587→| `use_shipping_as_billing` | boolean | No | Default: true. If true, billing_address is ignored and copied from shipping. |
   588→
   589→**Success response:** `200 OK`
   590→
   591→Returns the full checkout object. Status transitions to `addressed`. The response includes `available_shipping_methods` populated based on the shipping address and store's shipping zones.
   592→
   593→```json
   594→{
   595→  "id": 7,
   596→  "status": "addressed",
   597→  "shipping_address_json": {
   598→    "first_name": "Jane",
   599→    "last_name": "Doe",
   600→    "address1": "123 Main St",
   601→    "address2": "Apt 4B",
   602→    "city": "Berlin",
   603→    "province": "Berlin",
   604→    "province_code": "BE",
   605→    "country": "DE",
   606→    "country_code": "DE",
   607→    "postal_code": "10115",
   608→    "phone": "+49301234567"
   609→  },
   610→  "billing_address_json": {
   611→    "first_name": "Jane",
   612→    "last_name": "Doe",
   613→    "address1": "123 Main St",
   614→    "address2": "Apt 4B",
   615→    "city": "Berlin",
   616→    "province": "Berlin",
   617→    "province_code": "BE",
   618→    "country": "DE",
   619→    "country_code": "DE",
   620→    "postal_code": "10115",
   621→    "phone": "+49301234567"
   622→  },
   623→  "available_shipping_methods": [
   624→    {
   625→      "id": 1,
   626→      "name": "Standard Shipping",
   627→      "type": "flat",
   628→      "price_amount": 500,
   629→      "currency": "EUR",
   630→      "estimated_days_min": 3,
   631→      "estimated_days_max": 5
   632→    },
   633→    {
   634→      "id": 2,
   635→      "name": "Express Shipping",
   636→      "type": "flat",
   637→      "price_amount": 1200,
   638→      "currency": "EUR",
   639→      "estimated_days_min": 1,
   640→      "estimated_days_max": 2
   641→    }
   642→  ],
   643→  "totals": {
   644→    "subtotal": 5000,
   645→    "discount": 0,
   646→    "shipping": 0,
   647→    "tax": 0,
   648→    "total": 5000,
   649→    "currency": "EUR"
   650→  }
   651→}
   652→```
   653→
   654→**Error responses:**
   655→- `404` - Checkout not found
   656→- `410` - Checkout expired
   657→- `422` - Validation errors (missing required fields, invalid country code, address not in any shipping zone)
   658→
   659→---
   660→
   661→#### PUT /api/storefront/v1/checkouts/{checkoutId}/shipping-method
   662→
   663→Select a shipping method for the checkout.
   664→
   665→- **Authentication:** None
   666→- **Rate limit:** `checkout` (10/min per session)
   667→
   668→**Request body:**
   669→
   670→```json
   671→{
   672→  "shipping_method_id": 1
   673→}
   674→```
   675→
   676→| Field | Type | Required | Validation |
   677→|-------|------|----------|------------|
   678→| `shipping_method_id` | integer | Yes | Must be one of the available shipping methods for this checkout's address. |
   679→
   680→**Success response:** `200 OK`
   681→
   682→Returns the full checkout object. Status transitions to `shipping_selected`. Totals are recalculated including the shipping cost and taxes (if applicable).
   683→
   684→```json
   685→{
   686→  "id": 7,
   687→  "status": "shipping_selected",
   688→  "shipping_method_id": 1,
   689→  "totals": {
   690→    "subtotal": 5000,
   691→    "discount": 0,
   692→    "shipping": 500,
   693→    "tax": 1045,
   694→    "total": 6545,
   695→    "currency": "EUR"
   696→  },
   697→  "tax_provider_snapshot_json": {
   698→    "provider": "manual",
   699→    "calculated_at": "2026-02-11T10:05:00Z",
   700→    "lines": [
   701→      {
   702→        "variant_id": 101,
   703→        "tax_amount": 950,
   704→        "rate": 1900,
   705→        "jurisdiction": "DE"
   706→      }
   707→    ],
   708→    "shipping_tax_amount": 95,
   709→    "shipping_tax_rate": 1900
   710→  }
   711→}
   712→```
   713→
   714→**Error responses:**
   715→- `404` - Checkout not found
   716→- `410` - Checkout expired
   717→- `422` - Invalid shipping method (not available for this address/checkout)
   718→
   719→---
   720→
   721→#### PUT /api/storefront/v1/checkouts/{checkoutId}/payment-method
   722→
   723→Select a payment method for the checkout. This step records the chosen method without processing payment.
   724→
   725→- **Authentication:** None
   726→- **Rate limit:** `checkout` (10/min per session)
   727→
   728→**Request body:**
   729→
   730→```json
   731→{
   732→  "payment_method": "credit_card"
   733→}
   734→```
   735→
   736→| Field | Type | Required | Validation |
   737→|-------|------|----------|------------|
   738→| `payment_method` | string | Yes | Must be one of: `credit_card`, `paypal`, `bank_transfer`. |
   739→
   740→**Success response:** `200 OK`
   741→
   742→Returns the full checkout object. Status transitions to `payment_selected`.
   743→
   744→```json
   745→{
   746→  "id": 7,
   747→  "status": "payment_selected",
   748→  "payment_method": "credit_card",
   749→  "totals": {
   750→    "subtotal": 5000,
   751→    "discount": 0,
   752→    "shipping": 500,
   753→    "tax": 1045,
   754→    "total": 6545,
   755→    "currency": "EUR"
   756→  }
   757→}
   758→```
   759→
   760→**Error responses:**
   761→- `404` - Checkout not found
   762→- `410` - Checkout expired
   763→- `422` - Invalid payment method or checkout not in `shipping_selected` state
   764→
   765→---
   766→
   767→#### POST /api/storefront/v1/checkouts/{checkoutId}/apply-discount
   768→
   769→Apply a discount code to the checkout.
   770→
   771→- **Authentication:** None
   772→- **Rate limit:** `checkout` (10/min per session)
   773→
   774→**Request body:**
   775→
   776→```json
   777→{
   778→  "code": "WELCOME10"
   779→}
   780→```
   781→
   782→| Field | Type | Required | Validation |
   783→|-------|------|----------|------------|
   784→| `code` | string | Yes | Max: 50. Must be a valid, active discount code for this store. Case-insensitivity determined by store setting. |
   785→
   786→**Success response:** `200 OK`
   787→
   788→```json
   789→{
   790→  "id": 7,
   791→  "status": "shipping_selected",
   792→  "discount_code": "WELCOME10",
   793→  "totals": {
   794→    "subtotal": 5000,
   795→    "discount": 500,
   796→    "shipping": 500,
   797→    "tax": 950,
   798→    "total": 5950,
   799→    "currency": "EUR"
   800→  },
   801→  "applied_discounts": [
   802→    {
   803→      "code": "WELCOME10",
   804→      "type": "percent",
   805→      "value_amount": 10,
   806→      "applied_amount": 500,
   807→      "description": "10% off your first order"
   808→    }
   809→  ]
   810→}
   811→```
   812→
   813→**Error responses:**
   814→- `400` - Discount code expired (error code: `discount_expired`)
   815→- `400` - Discount usage limit reached (error code: `discount_usage_exceeded`)
   816→- `404` - Checkout not found
   817→- `410` - Checkout expired
   818→- `422` - Invalid code (not found, does not apply to these items)
   819→
   820→```json
   821→{
   822→  "message": "This discount code has expired.",
   823→  "error_code": "discount_expired"
   824→}
   825→```
   826→
   827→---
   828→
   829→#### DELETE /api/storefront/v1/checkouts/{checkoutId}/discount
   830→
   831→Remove the applied discount code from the checkout.
   832→
   833→- **Authentication:** None
   834→- **Rate limit:** `checkout` (10/min per session)
   835→
   836→**Success response:** `200 OK`
   837→
   838→Returns the full checkout object with discount removed and totals recalculated.
   839→
   840→**Error responses:**
   841→- `404` - Checkout not found or no discount applied
   842→- `410` - Checkout expired
   843→
   844→---
   845→
   846→### 2.3 Payment Endpoint
   847→
   848→#### POST /api/storefront/v1/checkouts/{checkoutId}/pay
   849→
   850→Process payment for a checkout using the selected payment method. All payment processing happens in-process via the Mock PSP - no external API calls, no redirects, no webhooks.
   851→
   852→- **Authentication:** None
   853→- **Rate limit:** `checkout` (10/min per session)
   854→
   855→**Request body (Credit Card):**
   856→
   857→```json
   858→{
   859→  "payment_method": "credit_card",
   860→  "card_number": "4242424242424242",
   861→  "card_expiry": "12/28",
   862→  "card_cvc": "123",
   863→  "card_holder": "Jane Doe"
   864→}
   865→```
   866→
   867→**Request body (PayPal):**
   868→
   869→```json
   870→{
   871→  "payment_method": "paypal"
   872→}
   873→```
   874→
   875→**Request body (Bank Transfer):**
   876→
   877→```json
   878→{
   879→  "payment_method": "bank_transfer"
   880→}
   881→```
   882→
   883→| Field | Type | Required | Validation |
   884→|-------|------|----------|------------|
   885→| `payment_method` | string | Yes | Must be one of: `credit_card`, `paypal`, `bank_transfer`. |
   886→| `card_number` | string | Conditional | Required when `payment_method = credit_card`. Digits only (spaces stripped). |
   887→| `card_expiry` | string | Conditional | Required when `payment_method = credit_card`. Format: MM/YY. |
   888→| `card_cvc` | string | Conditional | Required when `payment_method = credit_card`. 3-4 digits. |
   889→| `card_holder` | string | Conditional | Required when `payment_method = credit_card`. Max 255 chars. |
   890→
   891→**Success response (Credit Card / PayPal - instant capture):** `200 OK`
   892→
   893→```json
   894→{
   895→  "checkout_id": 7,
   896→  "status": "completed",
   897→  "order": {
   898→    "id": 42,
   899→    "order_number": "#1016",
   900→    "status": "paid",
   901→    "financial_status": "paid",
   902→    "payment_method": "credit_card",
   903→    "total_amount": 6545,
   904→    "currency": "EUR"
   905→  }
   906→}
   907→```
   908→
   909→**Success response (Bank Transfer - deferred payment):** `200 OK`
   910→
   911→```json
   912→{
   913→  "checkout_id": 7,
   914→  "status": "completed",
   915→  "order": {
   916→    "id": 42,
   917→    "order_number": "#1016",
   918→    "status": "pending",
   919→    "financial_status": "pending",
   920→    "payment_method": "bank_transfer",
   921→    "total_amount": 6545,
   922→    "currency": "EUR"
   923→  },
   924→  "bank_transfer_instructions": {
   925→    "bank_name": "Mock Bank AG",
   926→    "iban": "DE89 3704 0044 0532 0130 00",
   927→    "bic": "COBADEFFXXX",
   928→    "reference": "#1016",
   929→    "amount_formatted": "65.45 EUR"
   930→  }
   931→}
   932→```
   933→
   934→**Error responses:**
   935→- `404` - Checkout not found
   936→- `409` - Checkout not in a valid state for payment (must be `payment_selected`)
   937→- `422` - Validation errors (missing card fields, invalid payment method)
   938→- `422` - Payment declined (credit card magic numbers)
   939→
   940→```json
   941→{
   942→  "message": "Your card was declined.",
   943→  "error_code": "card_declined"
   944→}
   945→```
   946→
   947→**Magic card number error codes:**
   948→
   949→| Card Number | Error Code | Message |
   950→|---|---|---|
   951→| `4000000000000002` | `card_declined` | "Your card was declined." |
   952→| `4000000000009995` | `insufficient_funds` | "Your card has insufficient funds." |
   953→
   954→---
   955→
   956→### 2.4 Order Endpoints
   957→
   958→#### GET /api/storefront/v1/orders/{orderNumber}
   959→
   960→Retrieve order status. Access is controlled via a signed token (included in confirmation emails and the confirmation page URL).
   961→
   962→- **Authentication:** Tokenized access (`token` query parameter with HMAC signature)
   963→- **Rate limit:** `api.storefront` (120/min per IP)
   964→
   965→**Query parameters:**
   966→
   967→| Parameter | Type | Required | Description |
   968→|-----------|------|----------|-------------|
   969→| `token` | string | Yes | HMAC-signed access token for this order |
   970→
   971→**Success response:** `200 OK`
   972→
   973→```json
   974→{
   975→  "order_number": "#1001",
   976→  "status": "paid",
   977→  "financial_status": "paid",
   978→  "fulfillment_status": "unfulfilled",
   979→  "email": "customer@example.com",
   980→  "currency": "EUR",
   981→  "placed_at": "2026-02-11T10:10:00Z",
   982→  "lines": [
   983→    {
   984→      "title_snapshot": "Classic T-Shirt",
   985→      "variant_title": "Blue / Medium",
   986→      "sku_snapshot": "TSH-BLU-M",
   987→      "quantity": 2,
   988→      "unit_price_amount": 2500,
   989→      "total_amount": 5000
   990→    }
   991→  ],
   992→  "totals": {
   993→    "subtotal_amount": 5000,
   994→    "discount_amount": 500,
   995→    "shipping_amount": 500,
   996→    "tax_amount": 950,
   997→    "total_amount": 5950
   998→  },
   999→  "shipping_address": {
  1000→    "first_name": "Jane",
  1001→    "last_name": "Doe",
  1002→    "address1": "123 Main St",
  1003→    "city": "Berlin",
  1004→    "country": "DE",
  1005→    "postal_code": "10115"
  1006→  },
  1007→  "fulfillments": []
  1008→}
  1009→```
  1010→
  1011→**Error responses:**
  1012→- `401` - Invalid or missing token
  1013→- `404` - Order not found
  1014→
  1015→---
  1016→
  1017→### 2.5 Search Endpoints
  1018→
  1019→#### GET /api/storefront/v1/search
  1020→
  1021→Search products within the current store.
  1022→
  1023→- **Authentication:** None
  1024→- **Rate limit:** `search` (30/min per IP)
  1025→
  1026→**Query parameters:**
  1027→
  1028→| Parameter | Type | Required | Description |
  1029→|-----------|------|----------|-------------|
  1030→| `q` | string | Yes | Search query. Min: 1, max: 200 characters. |
  1031→| `filters` | json | No | URL-encoded JSON object with filter facets. |
  1032→| `sort` | string | No | One of: `relevance`, `price_asc`, `price_desc`, `newest`, `best_selling`. Default: `relevance`. |
  1033→| `page` | integer | No | Page number. Default: 1. |
  1034→| `per_page` | integer | No | Results per page. Min: 1, max: 50. Default: 24. |
  1035→
  1036→**Filters JSON schema:**
  1037→
  1038→```json
  1039→{
  1040→  "collection_id": 5,
  1041→  "price_min": 1000,
  1042→  "price_max": 5000,
  1043→  "in_stock": true,
  1044→  "tags": ["organic", "cotton"],
  1045→  "vendor": "Acme Apparel"
  1046→}
  1047→```
  1048→
  1049→**Success response:** `200 OK`
  1050→
  1051→```json
  1052→{
  1053→  "query": "t-shirt",
  1054→  "results": [
  1055→    {
  1056→      "id": 10,
  1057→      "title": "Classic T-Shirt",
  1058→      "handle": "classic-t-shirt",
  1059→      "vendor": "Acme Apparel",
  1060→      "product_type": "Apparel",
  1061→      "price_amount": 2500,
  1062→      "compare_at_amount": 3500,
  1063→      "currency": "EUR",
  1064→      "image_url": "https://cdn.example.com/products/tshirt.jpg",
  1065→      "in_stock": true,
  1066→      "tags": ["organic", "cotton"]
  1067→    }
  1068→  ],
  1069→  "facets": {
  1070→    "vendors": [
  1071→      { "value": "Acme Apparel", "count": 12 }
  1072→    ],
  1073→    "tags": [
  1074→      { "value": "organic", "count": 8 },
  1075→      { "value": "cotton", "count": 15 }
  1076→    ],
  1077→    "price_range": {
  1078→      "min": 1500,
  1079→      "max": 8500
  1080→    }
  1081→  },
  1082→  "pagination": {
  1083→    "current_page": 1,
  1084→    "per_page": 24,
  1085→    "total": 42,
  1086→    "last_page": 2
  1087→  }
  1088→}
  1089→```
  1090→
  1091→**Error responses:**
  1092→- `400` - Query too long (max 200 characters) or malformed filters
  1093→- `422` - Validation error
  1094→
  1095→---
  1096→
  1097→#### GET /api/storefront/v1/search/suggest
  1098→
  1099→Autocomplete suggestions as the user types.
  1100→
  1101→- **Authentication:** None
  1102→- **Rate limit:** `search` (30/min per IP)
  1103→
  1104→**Query parameters:**
  1105→
  1106→| Parameter | Type | Required | Description |
  1107→|-----------|------|----------|-------------|
  1108→| `q` | string | Yes | Search prefix. Min: 1, max: 100 characters. |
  1109→| `limit` | integer | No | Max suggestions. Default: 5, max: 10. |
  1110→
  1111→**Success response:** `200 OK`
  1112→
  1113→```json
  1114→{
  1115→  "query": "t-sh",
  1116→  "suggestions": [
  1117→    {
  1118→      "type": "product",
  1119→      "title": "Classic T-Shirt",
  1120→      "handle": "classic-t-shirt",
  1121→      "image_url": "https://cdn.example.com/products/tshirt-thumb.jpg",
  1122→      "price_amount": 2500,
  1123→      "currency": "EUR"
  1124→    },
  1125→    {
  1126→      "type": "collection",
  1127→      "title": "T-Shirts",
  1128→      "handle": "t-shirts",
  1129→      "image_url": null
  1130→    }
  1131→  ]
  1132→}
  1133→```
  1134→
  1135→**Error responses:**
  1136→- `400` - Query too long
  1137→- `422` - Validation error
  1138→
  1139→---
  1140→
  1141→### 2.6 Analytics Endpoints
  1142→
  1143→#### POST /api/storefront/v1/analytics/events
  1144→
  1145→Batch submit storefront analytics events.
  1146→
  1147→- **Authentication:** None
  1148→- **Rate limit:** `analytics` (60/min per IP)
  1149→
  1150→**Request body:**
  1151→
  1152→```json
  1153→{
  1154→  "events": [
  1155→    {
  1156→      "type": "page_view",
  1157→      "session_id": "sess_abc123",
  1158→      "client_event_id": "evt_001",
  1159→      "properties": {
  1160→        "url": "/products/classic-t-shirt",
  1161→        "referrer": "https://google.com",
  1162→        "user_agent": "Mozilla/5.0..."
  1163→      },
  1164→      "occurred_at": "2026-02-11T10:00:00Z"
  1165→    },
  1166→    {
  1167→      "type": "add_to_cart",
  1168→      "session_id": "sess_abc123",
  1169→      "client_event_id": "evt_002",
  1170→      "properties": {
  1171→        "product_id": 10,
  1172→        "variant_id": 101,
  1173→        "quantity": 1,
  1174→        "price_amount": 2500,
  1175→        "currency": "EUR"
  1176→      },
  1177→      "occurred_at": "2026-02-11T10:01:00Z"
  1178→    }
  1179→  ]
  1180→}
  1181→```
  1182→
  1183→| Field | Type | Required | Validation |
  1184→|-------|------|----------|------------|
  1185→| `events` | array | Yes | Min: 1, max: 50 events per batch. |
  1186→| `events[].type` | string | Yes | One of: `page_view`, `add_to_cart`, `remove_from_cart`, `checkout_started`, `checkout_completed`, `search`. |
  1187→| `events[].session_id` | string | Yes | Max: 100 characters. |
  1188→| `events[].client_event_id` | string | Yes | Max: 100 characters. If a `client_event_id` has already been recorded for the current store, the duplicate event is silently dropped (unique on `(store_id, client_event_id)`). |
  1189→| `events[].properties` | object | No | Freeform JSON. Max depth: 3 levels. |
  1190→| `events[].occurred_at` | string (ISO 8601) | Yes | Must not be more than 1 hour in the past or in the future. |
  1191→
  1192→**Success response:** `202 Accepted`
  1193→
  1194→```json
  1195→{
  1196→  "accepted": 2,
  1197→  "rejected": 0
  1198→}
  1199→```
  1200→
  1201→**Error responses:**
  1202→- `422` - Validation errors (invalid event types, too many events, etc.)
  1203→
  1204→---
  1205→
  1206→## 3. Admin REST API
  1207→
  1208→**Prefix:** `/api/admin/v1`
  1209→**Middleware:** `auth:sanctum`, `store.resolve`, `throttle:api.admin`
  1210→**Content-Type:** `application/json`
  1211→
  1212→These endpoints provide programmatic access for integrations, CLI tools, and third-party apps. The admin web UI uses Livewire actions directly and does not call these endpoints.
  1213→
  1214→All endpoints require a valid Sanctum API token with appropriate scopes. Responses follow Laravel's API Resource conventions.
  1215→
  1216→---
  1217→
  1218→### 3.1 Platform Management (Super-Admin Only)
  1219→
  1220→These endpoints are restricted to users with the `owner` (platform) role.
  1221→
  1222→#### POST /api/admin/v1/platform/organizations
  1223→
  1224→Create a new organization.
  1225→
  1226→- **Authentication:** Sanctum token with `manage-platform` scope
  1227→- **Rate limit:** `api.admin` (60/min per token)
  1228→
  1229→**Request body:**
  1230→
  1231→```json
  1232→{
  1233→  "name": "Acme Corp",
  1234→  "billing_email": "billing@acme.com"
  1235→}
  1236→```
  1237→
  1238→| Field | Type | Required | Validation |
  1239→|-------|------|----------|------------|
  1240→| `name` | string | Yes | Max: 255 |
  1241→| `billing_email` | string | Yes | Valid email, max: 255 |
  1242→
  1243→**Success response:** `201 Created`
  1244→
  1245→```json
  1246→{
  1247→  "data": {
  1248→    "id": 1,
  1249→    "name": "Acme Corp",
  1250→    "billing_email": "billing@acme.com",
  1251→    "created_at": "2026-02-11T10:00:00Z",
  1252→    "updated_at": "2026-02-11T10:00:00Z"
  1253→  }
  1254→}
  1255→```
  1256→
  1257→**Error responses:**
  1258→- `403` - Insufficient permissions
  1259→- `422` - Validation errors
  1260→
  1261→---
  1262→
  1263→#### POST /api/admin/v1/platform/stores
  1264→
  1265→Create a new store within an organization.
  1266→
  1267→- **Authentication:** Sanctum token with `manage-platform` scope
  1268→- **Rate limit:** `api.admin` (60/min per token)
  1269→
  1270→**Request body:**
  1271→
  1272→```json
  1273→{
  1274→  "organization_id": 1,
  1275→  "name": "Acme Store",
  1276→  "handle": "acme",
  1277→  "default_currency": "EUR",
  1278→  "default_locale": "en",
  1279→  "timezone": "Europe/Berlin"
  1280→}
  1281→```
  1282→
  1283→| Field | Type | Required | Validation |
  1284→|-------|------|----------|------------|
  1285→| `organization_id` | integer | Yes | Must exist |
  1286→| `name` | string | Yes | Max: 255 |
  1287→| `handle` | string | Yes | Unique, lowercase, alphanumeric with hyphens. Max: 63. Regex: `^[a-z0-9]([a-z0-9-]*[a-z0-9])?$` |
  1288→| `default_currency` | string | Yes | Valid ISO 4217 code |
  1289→| `default_locale` | string | Yes | Valid locale code (e.g., `en`, `de`, `fr`) |
  1290→| `timezone` | string | Yes | Valid IANA timezone |
  1291→
  1292→**Success response:** `201 Created`
  1293→
  1294→```json
  1295→{
  1296→  "data": {
  1297→    "id": 1,
  1298→    "organization_id": 1,
  1299→    "name": "Acme Store",
  1300→    "handle": "acme",
  1301→    "status": "active",
  1302→    "default_currency": "EUR",
  1303→    "default_locale": "en",
  1304→    "timezone": "Europe/Berlin",
  1305→    "created_at": "2026-02-11T10:00:00Z"
  1306→  }
  1307→}
  1308→```
  1309→
  1310→**Error responses:**
  1311→- `403` - Insufficient permissions
  1312→- `422` - Validation errors (duplicate handle, invalid timezone, etc.)
  1313→
  1314→---
  1315→
  1316→#### POST /api/admin/v1/stores/{storeId}/invites
  1317→
  1318→Invite a user to a store with a specific role.
  1319→
  1320→- **Authentication:** Sanctum token with `manage-platform` scope
  1321→- **Rate limit:** `api.admin` (60/min per token)
  1322→
  1323→**Request body:**
  1324→
  1325→```json
  1326→{
  1327→  "email": "staff@acme.com",
  1328→  "role": "staff"
  1329→}
  1330→```
  1331→
  1332→| Field | Type | Required | Validation |
  1333→|-------|------|----------|------------|
  1334→| `email` | string | Yes | Valid email, max: 255 |
  1335→| `role` | string | Yes | One of: `owner`, `admin`, `staff`, `support` |
  1336→
  1337→**Success response:** `201 Created`
  1338→
  1339→```json
  1340→{
  1341→  "data": {
  1342→    "email": "staff@acme.com",
  1343→    "role": "staff",
  1344→    "invited_at": "2026-02-11T10:00:00Z",
  1345→    "expires_at": "2026-02-18T10:00:00Z"
  1346→  }
  1347→}
  1348→```
  1349→
  1350→**Error responses:**
  1351→- `403` - Insufficient permissions (only `owner` and `admin` can invite)
  1352→- `409` - User already a member of this store
  1353→- `422` - Validation errors
  1354→
  1355→---
  1356→
  1357→#### GET /api/admin/v1/stores/{storeId}/me
  1358→
  1359→Get the current user's membership details for a store.
  1360→
  1361→- **Authentication:** Sanctum token
  1362→- **Rate limit:** `api.admin` (60/min per token)
  1363→
  1364→**Success response:** `200 OK`
  1365→
  1366→```json
  1367→{
  1368→  "data": {
  1369→    "user_id": 3,
  1370→    "store_id": 1,
  1371→    "role": "admin",
  1372→    "email": "admin@acme.com",
  1373→    "name": "Jane Admin",
  1374→    "permissions": [
  1375→      "read-products",
  1376→      "write-products",
  1377→      "read-orders",
  1378→      "write-orders",
  1379→      "read-settings",
  1380→      "write-settings",
  1381→      "read-customers"
  1382→    ]
  1383→  }
  1384→}
  1385→```
  1386→
  1387→**Error responses:**
  1388→- `403` - Not a member of this store
  1389→- `404` - Store not found
  1390→
  1391→---
  1392→
  1393→### 3.2 Products
  1394→
  1395→#### Admin Products Endpoint Summary
  1396→
  1397→| Method | Path | Auth Scope | Description | Rate Limit |
  1398→|--------|------|------------|-------------|------------|
  1399→| GET | `/api/admin/v1/stores/{storeId}/products` | `read-products` | List products with filtering and pagination | `api.admin` (60/min) |
  1400→| POST | `/api/admin/v1/stores/{storeId}/products` | `write-products` | Create a new product | `api.admin` (60/min) |
  1401→| GET | `/api/admin/v1/stores/{storeId}/products/{productId}` | `read-products` | Get single product with all relations | `api.admin` (60/min) |
  1402→| PUT | `/api/admin/v1/stores/{storeId}/products/{productId}` | `write-products` | Update a product (partial updates supported) | `api.admin` (60/min) |
  1403→| DELETE | `/api/admin/v1/stores/{storeId}/products/{productId}` | `write-products` | Archive a product (soft delete) | `api.admin` (60/min) |
  1404→| POST | `/api/admin/v1/stores/{storeId}/products/{productId}/media/presign-upload` | `write-products` | Generate presigned URL for media upload | `api.admin` (60/min) |
  1405→
  1406→#### GET /api/admin/v1/stores/{storeId}/products
  1407→
  1408→List products with filtering and pagination.
  1409→
  1410→**Query parameters:**
  1411→
  1412→| Parameter | Type | Required | Description |
  1413→|-----------|------|----------|-------------|
  1414→| `status` | string | No | Filter by status: `draft`, `active`, `archived`. |
  1415→| `query` | string | No | Search by title, vendor, or SKU. |
  1416→| `collection_id` | integer | No | Filter by collection membership. |
  1417→| `page` | integer | No | Page number. Default: 1. |
  1418→| `per_page` | integer | No | Results per page. Default: 25, max: 100. |
  1419→| `sort` | string | No | One of: `title_asc`, `title_desc`, `created_at_asc`, `created_at_desc`, `updated_at_desc`. Default: `updated_at_desc`. |
  1420→
  1421→**Success response:** `200 OK`
  1422→
  1423→```json
  1424→{
  1425→  "data": [
  1426→    {
  1427→      "id": 10,
  1428→      "store_id": 1,
  1429→      "title": "Classic T-Shirt",
  1430→      "handle": "classic-t-shirt",
  1431→      "status": "active",
  1432→      "vendor": "Acme Apparel",
  1433→      "product_type": "Apparel",
  1434→      "tags": ["organic", "cotton"],
  1435→      "variants_count": 6,
  1436→      "total_inventory": 150,
  1437→      "published_at": "2026-02-10T08:00:00Z",
  1438→      "created_at": "2026-02-09T14:00:00Z",
  1439→      "updated_at": "2026-02-11T09:00:00Z",
  1440→      "featured_image": {
  1441→        "url": "https://cdn.example.com/products/tshirt.jpg",
  1442→        "alt_text": "Classic T-Shirt in Blue"
  1443→      }
  1444→    }
  1445→  ],
  1446→  "meta": {
  1447→    "current_page": 1,
  1448→    "per_page": 25,
  1449→    "total": 42,
  1450→    "last_page": 2
  1451→  }
  1452→}
  1453→```
  1454→
  1455→---
  1456→
  1457→#### POST /api/admin/v1/stores/{storeId}/products
  1458→
  1459→Create a new product.
  1460→
  1461→**Request body:**
  1462→
  1463→```json
  1464→{
  1465→  "title": "Classic T-Shirt",
  1466→  "handle": "classic-t-shirt",
  1467→  "description_html": "<p>A comfortable cotton t-shirt.</p>",
  1468→  "vendor": "Acme Apparel",
  1469→  "product_type": "Apparel",
  1470→  "status": "draft",
  1471→  "tags": ["organic", "cotton"],
  1472→  "options": [
  1473→    { "name": "Color", "position": 1 },
  1474→    { "name": "Size", "position": 2 }
  1475→  ],
  1476→  "variants": [
  1477→    {
  1478→      "sku": "TSH-BLU-S",
  1479→      "barcode": "1234567890123",
  1480→      "price_amount": 2500,
  1481→      "compare_at_amount": 3500,
  1482→      "currency": "EUR",
  1483→      "weight_g": 200,
  1484→      "requires_shipping": true,
  1485→      "is_default": true,
  1486→      "position": 1,
  1487→      "status": "active",
  1488→      "option_values": [
  1489→        { "option_name": "Color", "value": "Blue" },
  1490→        { "option_name": "Size", "value": "Small" }
  1491→      ],
  1492→      "inventory": {
  1493→        "quantity_on_hand": 50,
  1494→        "policy": "deny"
  1495→      }
  1496→    }
  1497→  ],
  1498→  "collections": [5]
  1499→}
  1500→```
  1501→
  1502→**Product fields:**
  1503→
  1504→| Field | Type | Required | Validation |
  1505→|-------|------|----------|------------|
  1506→| `title` | string | Yes | Max: 255 |
  1507→| `handle` | string | No | Auto-generated from title if not provided. Unique per store. Lowercase, hyphens only. Max: 255. |
  1508→| `description_html` | string | No | Max: 65535 |
  1509→| `vendor` | string | No | Max: 255 |
  1510→| `product_type` | string | No | Max: 255 |
  1511→| `status` | string | No | One of: `draft`, `active`. Default: `draft`. |
  1512→| `tags` | array of strings | No | Max: 50 tags, each max: 255. |
  1513→| `options` | array | No | Max: 3 options. |
  1514→| `options[].name` | string | Yes | Max: 255 |
  1515→| `options[].position` | integer | Yes | Min: 1, max: 3 |
  1516→| `collections` | array of integers | No | Collection IDs to add this product to. |
  1517→
  1518→**Variant fields:**
  1519→
  1520→| Field | Type | Required | Validation |
  1521→|-------|------|----------|------------|
  1522→| `variants` | array | Yes | At least one variant required. Max: 100. |
  1523→| `variants[].sku` | string | Yes | Max: 255. Unique per store (`store_id`, `sku`). |
  1524→| `variants[].barcode` | string | No | Max: 255 |
  1525→| `variants[].price_amount` | integer | Yes | Min: 0 (in minor currency units) |
  1526→| `variants[].compare_at_amount` | integer | No | Min: 0. Must be > price_amount if set. |
  1527→| `variants[].currency` | string | No | Defaults to store currency. |
  1528→| `variants[].weight_g` | integer | No | Min: 0 |
  1529→| `variants[].requires_shipping` | boolean | No | Default: true |
  1530→| `variants[].is_default` | boolean | No | Exactly one variant must be default. |
  1531→| `variants[].position` | integer | No | Auto-assigned if not provided. |
  1532→| `variants[].status` | string | No | One of: `active`, `archived`. Default: `active`. |
  1533→| `variants[].option_values` | array | No | Must match defined options. |
  1534→| `variants[].inventory.quantity_on_hand` | integer | No | Default: 0. Min: 0. |
  1535→| `variants[].inventory.policy` | string | No | One of: `deny`, `continue`. Default: `deny`. |
  1536→
  1537→**Success response:** `201 Created`
  1538→
  1539→Returns the full product resource with nested variants, options, and media.
  1540→
  1541→**Error responses:**
  1542→- `403` - Insufficient permissions
  1543→- `422` - Validation errors (duplicate SKU, duplicate handle, missing required fields)
  1544→
  1545→---
  1546→
  1547→#### GET /api/admin/v1/stores/{storeId}/products/{productId}
  1548→
  1549→Get a single product with all variants, options, media, and collections.
  1550→
  1551→**Success response:** `200 OK`
  1552→
  1553→```json
  1554→{
  1555→  "data": {
  1556→    "id": 10,
  1557→    "store_id": 1,
  1558→    "title": "Classic T-Shirt",
  1559→    "handle": "classic-t-shirt",
  1560→    "description_html": "<p>A comfortable cotton t-shirt.</p>",
  1561→    "vendor": "Acme Apparel",
  1562→    "product_type": "Apparel",
  1563→    "status": "active",
  1564→    "tags": ["organic", "cotton"],
  1565→    "published_at": "2026-02-10T08:00:00Z",
  1566→    "created_at": "2026-02-09T14:00:00Z",
  1567→    "updated_at": "2026-02-11T09:00:00Z",
  1568→    "options": [
  1569→      {
  1570→        "id": 1,
  1571→        "name": "Color",
  1572→        "position": 1,
  1573→        "values": [
  1574→          { "id": 1, "value": "Blue", "position": 1 },
  1575→          { "id": 2, "value": "Red", "position": 2 }
  1576→        ]
  1577→      },
  1578→      {
  1579→        "id": 2,
  1580→        "name": "Size",
  1581→        "position": 2,
  1582→        "values": [
  1583→          { "id": 3, "value": "Small", "position": 1 },
  1584→          { "id": 4, "value": "Medium", "position": 2 },
  1585→          { "id": 5, "value": "Large", "position": 3 }
  1586→        ]
  1587→      }
  1588→    ],
  1589→    "variants": [
  1590→      {
  1591→        "id": 101,
  1592→        "sku": "TSH-BLU-S",
  1593→        "barcode": "1234567890123",
  1594→        "price_amount": 2500,
  1595→        "compare_at_amount": 3500,
  1596→        "currency": "EUR",
  1597→        "weight_g": 200,
  1598→        "requires_shipping": true,
  1599→        "is_default": true,
  1600→        "position": 1,
  1601→        "status": "active",
  1602→        "option_values": [
  1603→          { "option_name": "Color", "value": "Blue" },
  1604→          { "option_name": "Size", "value": "Small" }
  1605→        ],
  1606→        "inventory": {
  1607→          "quantity_on_hand": 50,
  1608→          "quantity_reserved": 2,
  1609→          "policy": "deny"
  1610→        }
  1611→      }
  1612→    ],
  1613→    "media": [
  1614→      {
  1615→        "id": 1,
  1616→        "type": "image",
  1617→        "storage_key": "stores/1/products/10/main.jpg",
  1618→        "url": "https://cdn.example.com/stores/1/products/10/main.jpg",
  1619→        "alt_text": "Classic T-Shirt in Blue",
  1620→        "width": 1200,
  1621→        "height": 1600,
  1622→        "mime_type": "image/jpeg",
  1623→        "byte_size": 245000,
  1624→        "position": 1,
  1625→        "status": "ready"
  1626→      }
  1627→    ],
  1628→    "collections": [
  1629→      {
  1630→        "id": 5,
  1631→        "title": "Summer Collection",
  1632→        "handle": "summer-collection"
  1633→      }
  1634→    ]
  1635→  }
  1636→}
  1637→```
  1638→
  1639→**Error responses:**
  1640→- `403` - Insufficient permissions
  1641→- `404` - Product not found (or belongs to another store)
  1642→
  1643→---
  1644→
  1645→#### PUT /api/admin/v1/stores/{storeId}/products/{productId}
  1646→
  1647→Update a product. Supports partial updates.
  1648→
  1649→Same fields as POST. Only provided fields are updated. Variants can be added, updated (by ID), or removed.
  1650→
  1651→**Example partial update request:**
  1652→
  1653→```json
  1654→{
  1655→  "title": "Classic Cotton T-Shirt",
  1656→  "status": "active",
  1657→  "tags": ["organic", "cotton", "bestseller"]
  1658→}
  1659→```
  1660→
  1661→**Success response:** `200 OK`
  1662→
  1663→Returns the full updated product resource.
  1664→
  1665→**Error responses:**
  1666→- `403` - Insufficient permissions
  1667→- `404` - Product not found
  1668→- `422` - Validation errors
  1669→
  1670→---
  1671→
  1672→#### DELETE /api/admin/v1/stores/{storeId}/products/{productId}
  1673→
  1674→Archive a product (soft delete). Products with existing orders cannot be hard-deleted.
  1675→
  1676→**Success response:** `200 OK`
  1677→
  1678→```json
  1679→{
  1680→  "data": {
  1681→    "id": 10,
  1682→    "status": "archived",
  1683→    "updated_at": "2026-02-11T11:00:00Z"
  1684→  }
  1685→}
  1686→```
  1687→
  1688→**Error responses:**
  1689→- `403` - Insufficient permissions
  1690→- `404` - Product not found
  1691→
  1692→---
  1693→
  1694→#### POST /api/admin/v1/stores/{storeId}/products/{productId}/media/presign-upload
  1695→
  1696→Generate a presigned URL for uploading product media directly to object storage (S3).
  1697→
  1698→**Request body:**
  1699→
  1700→```json
  1701→{
  1702→  "filename": "tshirt-front.jpg",
  1703→  "content_type": "image/jpeg",
  1704→  "byte_size": 245000
  1705→}
  1706→```
  1707→
  1708→| Field | Type | Required | Validation |
  1709→|-------|------|----------|------------|
  1710→| `filename` | string | Yes | Max: 255. Must have a valid extension. |
  1711→| `content_type` | string | Yes | One of: `image/jpeg`, `image/png`, `image/webp`, `image/avif`, `video/mp4`. |
  1712→| `byte_size` | integer | Yes | Min: 1, max: configurable (default 50MB for images, 500MB for video). |
  1713→
  1714→**Success response:** `201 Created`
  1715→
  1716→```json
  1717→{
  1718→  "upload_url": "https://s3.example.com/...presigned...",
  1719→  "method": "PUT",
  1720→  "headers": {
  1721→    "Content-Type": "image/jpeg"
  1722→  },
  1723→  "storage_key": "stores/1/products/10/media/uuid.jpg",
  1724→  "media_id": 2,
  1725→  "expires_at": "2026-02-11T10:10:00Z"
  1726→}
  1727→```
  1728→
  1729→**Error responses:**
  1730→- `403` - Insufficient permissions
  1731→- `404` - Product not found
  1732→- `422` - Validation errors (invalid content type, file too large)
  1733→
  1734→---
  1735→
  1736→### 3.3 Collections
  1737→
  1738→#### Admin Collections Endpoint Summary
  1739→
  1740→| Method | Path | Auth Scope | Description | Rate Limit |
  1741→|--------|------|------------|-------------|------------|
  1742→| GET | `/api/admin/v1/stores/{storeId}/collections` | `read-collections` | List collections | `api.admin` (60/min) |
  1743→| POST | `/api/admin/v1/stores/{storeId}/collections` | `write-collections` | Create a collection | `api.admin` (60/min) |
  1744→| PUT | `/api/admin/v1/stores/{storeId}/collections/{collectionId}` | `write-collections` | Update a collection | `api.admin` (60/min) |
  1745→| DELETE | `/api/admin/v1/stores/{storeId}/collections/{collectionId}` | `write-collections` | Delete a collection | `api.admin` (60/min) |
  1746→
  1747→#### GET /api/admin/v1/stores/{storeId}/collections
  1748→
  1749→List collections.
  1750→
  1751→**Query parameters:**
  1752→
  1753→| Parameter | Type | Required | Description |
  1754→|-----------|------|----------|-------------|
  1755→| `status` | string | No | Filter by status: `draft`, `active`, `archived`. |
  1756→| `query` | string | No | Search by title. |
  1757→| `page` | integer | No | Default: 1. |
  1758→| `per_page` | integer | No | Default: 25, max: 100. |
  1759→
  1760→**Success response:** `200 OK`
  1761→
  1762→```json
  1763→{
  1764→  "data": [
  1765→    {
  1766→      "id": 5,
  1767→      "store_id": 1,
  1768→      "title": "Summer Collection",
  1769→      "handle": "summer-collection",
  1770→      "description_html": "<p>Best items for summer.</p>",
  1771→      "type": "manual",
  1772→      "status": "active",
  1773→      "products_count": 15,
  1774→      "created_at": "2026-02-01T10:00:00Z",
  1775→      "updated_at": "2026-02-10T14:00:00Z"
  1776→    }
  1777→  ],
  1778→  "meta": {
  1779→    "current_page": 1,
  1780→    "per_page": 25,
  1781→    "total": 5,
  1782→    "last_page": 1
  1783→  }
  1784→}
  1785→```
  1786→
  1787→---
  1788→
  1789→#### POST /api/admin/v1/stores/{storeId}/collections
  1790→
  1791→Create a collection.
  1792→
  1793→**Request body:**
  1794→
  1795→```json
  1796→{
  1797→  "title": "Winter Collection",
  1798→  "handle": "winter-collection",
  1799→  "description_html": "<p>Cozy items for winter.</p>",
  1800→  "type": "manual",
  1801→  "status": "active",
  1802→  "product_ids": [10, 11]
  1803→}
  1804→```
  1805→
  1806→| Field | Type | Required | Validation |
  1807→|-------|------|----------|------------|
  1808→| `title` | string | Yes | Max: 255 |
  1809→| `handle` | string | No | Auto-generated from title. Unique per store. |
  1810→| `description_html` | string | No | Max: 65535 |
  1811→| `type` | string | Yes | One of: `manual`, `automated`. |
  1812→| `status` | string | No | One of: `draft`, `active`, `archived`. Default: `active`. |
  1813→| `product_ids` | array of integers | No | Products to add to the collection. |
  1814→
  1815→**Success response:** `201 Created`
  1816→
  1817→Returns the full collection resource.
  1818→
  1819→**Error responses:**
  1820→- `403` - Insufficient permissions
  1821→- `422` - Validation errors
  1822→
  1823→---
  1824→
  1825→#### PUT /api/admin/v1/stores/{storeId}/collections/{collectionId}
  1826→
  1827→Update a collection.
  1828→
  1829→Same fields as POST (all optional). Supports partial updates. Use `product_ids` to replace the full product set, or `add_product_ids` / `remove_product_ids` for incremental changes.
  1830→
  1831→**Success response:** `200 OK`
  1832→
  1833→**Error responses:**
  1834→- `403` - Insufficient permissions
  1835→- `404` - Collection not found
  1836→- `422` - Validation errors
  1837→
  1838→---
  1839→
  1840→#### DELETE /api/admin/v1/stores/{storeId}/collections/{collectionId}
  1841→
  1842→Delete a collection.
  1843→
  1844→**Authentication:** Bearer token with `write-collections` ability.
  1845→
  1846→**Success response:** `200 OK`
  1847→
  1848→```json
  1849→{
  1850→  "message": "Collection deleted"
  1851→}
  1852→```
  1853→
  1854→**Error responses:**
  1855→- `403` - Insufficient permissions
  1856→- `404` - Collection not found
  1857→
  1858→---
  1859→
  1860→### 3.4 Orders
  1861→
  1862→#### Admin Orders Endpoint Summary
  1863→
  1864→| Method | Path | Auth Scope | Description | Rate Limit |
  1865→|--------|------|------------|-------------|------------|
  1866→| GET | `/api/admin/v1/stores/{storeId}/orders` | `read-orders` | List orders with filtering and pagination | `api.admin` (60/min) |
  1867→| GET | `/api/admin/v1/stores/{storeId}/orders/{orderId}` | `read-orders` | Get full order details | `api.admin` (60/min) |
  1868→| POST | `/api/admin/v1/stores/{storeId}/orders/{orderId}/fulfillments` | `write-orders` | Create a fulfillment (ship items) | `api.admin` (60/min) |
  1869→| POST | `/api/admin/v1/stores/{storeId}/orders/{orderId}/refunds` | `write-orders` | Create a refund (partial or full) | `api.admin` (60/min) |
  1870→
  1871→#### GET /api/admin/v1/stores/{storeId}/orders
  1872→
  1873→List orders with filtering and pagination.
  1874→
  1875→**Query parameters:**
  1876→
  1877→| Parameter | Type | Required | Description |
  1878→|-----------|------|----------|-------------|
  1879→| `status` | string | No | Filter: `pending`, `paid`, `fulfilled`, `cancelled`, `refunded`. |
  1880→| `financial_status` | string | No | Filter: `pending`, `paid`, `partially_refunded`, `refunded`. |
  1881→| `fulfillment_status` | string | No | Filter: `unfulfilled`, `partial`, `fulfilled`. |
  1882→| `customer_id` | integer | No | Filter by customer. |
  1883→| `created_after` | string (ISO 8601) | No | Orders placed after this date. |
  1884→| `created_before` | string (ISO 8601) | No | Orders placed before this date. |
  1885→| `query` | string | No | Search by order number or customer email. |
  1886→| `page` | integer | No | Default: 1. |
  1887→| `per_page` | integer | No | Default: 25, max: 100. |
  1888→| `sort` | string | No | One of: `placed_at_desc`, `placed_at_asc`, `total_desc`, `total_asc`. Default: `placed_at_desc`. |
  1889→
  1890→**Success response:** `200 OK`
  1891→
  1892→```json
  1893→{
  1894→  "data": [
  1895→    {
  1896→      "id": 50,
  1897→      "order_number": "#1001",
  1898→      "status": "paid",
  1899→      "financial_status": "paid",
  1900→      "fulfillment_status": "unfulfilled",
  1901→      "customer": {
  1902→        "id": 30,
  1903→        "name": "Jane Doe",
  1904→        "email": "jane@example.com"
  1905→      },
  1906→      "currency": "EUR",
  1907→      "subtotal_amount": 5000,
  1908→      "discount_amount": 500,
  1909→      "shipping_amount": 500,
  1910→      "tax_amount": 950,
  1911→      "total_amount": 5950,
  1912→      "line_count": 1,
  1913→      "placed_at": "2026-02-11T10:10:00Z",
  1914→      "created_at": "2026-02-11T10:10:00Z"
  1915→    }
  1916→  ],
  1917→  "meta": {
  1918→    "current_page": 1,
  1919→    "per_page": 25,
  1920→    "total": 156,
  1921→    "last_page": 7
  1922→  }
  1923→}
  1924→```
  1925→
  1926→---
  1927→
  1928→#### GET /api/admin/v1/stores/{storeId}/orders/{orderId}
  1929→
  1930→Get full order details.
  1931→
  1932→**Success response:** `200 OK`
  1933→
  1934→```json
  1935→{
  1936→  "data": {
  1937→    "id": 50,
  1938→    "store_id": 1,
  1939→    "order_number": "#1001",
  1940→    "status": "paid",
  1941→    "financial_status": "paid",
  1942→    "fulfillment_status": "unfulfilled",
  1943→    "customer": {
  1944→      "id": 30,
  1945→      "name": "Jane Doe",
  1946→      "email": "jane@example.com"
  1947→    },
  1948→    "email": "jane@example.com",
  1949→    "currency": "EUR",
  1950→    "subtotal_amount": 5000,
  1951→    "discount_amount": 500,
  1952→    "shipping_amount": 500,
  1953→    "tax_amount": 950,
  1954→    "total_amount": 5950,
  1955→    "billing_address_json": {
  1956→      "first_name": "Jane",
  1957→      "last_name": "Doe",
  1958→      "address1": "123 Main St",
  1959→      "city": "Berlin",
  1960→      "country_code": "DE",
  1961→      "postal_code": "10115"
  1962→    },
  1963→    "shipping_address_json": {
  1964→      "first_name": "Jane",
  1965→      "last_name": "Doe",
  1966→      "address1": "123 Main St",
  1967→      "city": "Berlin",
  1968→      "country_code": "DE",
  1969→      "postal_code": "10115"
  1970→    },
  1971→    "lines": [
  1972→      {
  1973→        "id": 301,
  1974→        "product_id": 10,
  1975→        "variant_id": 101,
  1976→        "title_snapshot": "Classic T-Shirt",
  1977→        "sku_snapshot": "TSH-BLU-M",
  1978→        "quantity": 2,
  1979→        "unit_price_amount": 2500,
  1980→        "total_amount": 5000,
  1981→        "tax_lines_json": [
  1982→          { "rate": 1900, "amount": 950, "jurisdiction": "DE" }
  1983→        ],
  1984→        "discount_allocations_json": [
  1985→          { "code": "WELCOME10", "amount": 500 }
  1986→        ]
  1987→      }
  1988→    ],
  1989→    "payments": [
  1990→      {
  1991→        "id": 401,
  1992→        "provider": "mock",
  1993→        "method": "credit_card",
  1994→        "provider_payment_id": "mock_abc123",
  1995→        "status": "captured",
  1996→        "amount": 5950,
  1997→        "currency": "EUR",
  1998→        "created_at": "2026-02-11T10:10:00Z"
  1999→      }
  2000→    ],

prompt:
Read ALL the specification files in /Users/fabianwesner/Herd/shop/specs/ directory. I need the full content of:
1. specs/01-DATABASE-SCHEMA.md (read in chunks if needed)
2. specs/02-API-ROUTES.md (read in chunks if needed)
3. specs/03-ADMIN-UI.md (read in chunks if needed)
4. specs/04-STOREFRONT-UI.md (read in chunks if needed)
5. specs/05-BUSINESS-LOGIC.md (read in chunks if needed)
6. specs/06-AUTH-AND-SECURITY.md (read in chunks if needed)
7. specs/07-SEEDERS-AND-TEST-DATA.md (read in chunks if needed)
8. specs/08-PLAYWRIGHT-E2E-PLAN.md (read in chunks if needed)

For each file provide a comprehensive summary including:
- All table/column definitions
- All API endpoints
- All UI components and their specifications
- All business rules
- All test specifications
- All seeder data definitions

This is critical - I need ALL the details, not just summaries. Extract every table name, column, relationship, route, component, and test case.


prompt:
Read ALL the specification files in /Users/fabianwesner/Herd/shop/specs/ directory. I need the full content of:
1. specs/01-DATABASE-SCHEMA.md (read in chunks if needed)
2. specs/02-API-ROUTES.md (read in chunks if needed)
3. specs/03-ADMIN-UI.md (read in chunks if needed)
4. specs/04-STOREFRONT-UI.md (read in chunks if needed)
5. specs/05-BUSINESS-LOGIC.md (read in chunks if needed)
6. specs/06-AUTH-AND-SECURITY.md (read in chunks if needed)
7. specs/07-SEEDERS-AND-TEST-DATA.md (read in chunks if needed)
8. specs/08-PLAYWRIGHT-E2E-PLAN.md (read in chunks if needed)

For each file provide a comprehensive summary including:
- All table/column definitions
- All API endpoints
- All UI components and their specifications
- All business rules
- All test specifications
- All seeder data definitions

This is critical - I need ALL the details, not just summaries. Extract every table name, column, relationship, route, component, and test case.