/bin/zsh
-lc
sed -n '1,260p' specs/03-ADMIN-UI.md

# 03 - Admin Panel UI/UX Specification

This document defines every view, component, and interaction for the admin panel. The admin is entirely server-rendered using Laravel 12 + Livewire v4 + Flux UI Free components + Tailwind CSS v4. There is no SPA or client-side router.

**Flux UI Free components available:** avatar, badge, brand, breadcrumbs, button, callout, checkbox, dropdown, field, heading, icon, input, modal, navbar, profile, radio, select, separator, switch, text, textarea, tooltip

Anything not covered by Flux UI Free uses plain Tailwind CSS v4 markup with Livewire directives.

---

## 1. Admin Layout Shell

The admin layout is a persistent shell that wraps every admin page. It consists of a left sidebar, a top bar, and a main content area.

### 1.1 Route Prefix

All admin routes live under `/admin` and are protected by authentication middleware plus store-scoping middleware. The store is resolved from the session or a store-selector in the top bar.

### 1.2 Sidebar Navigation

**Livewire component:** `App\Livewire\Admin\Layout\Sidebar`

**Blade view:** `resources/views/livewire/admin/layout/sidebar.blade.php`

#### Component Requirements

| Property | Type | Purpose |
|----------|------|---------|
| `collapsed` | `bool` | Whether the sidebar is collapsed (mobile state) |
| `currentRoute` | `string` | The current route name for active highlighting |

| Action | Behavior |
|--------|----------|
| `toggle()` | Toggles the `collapsed` state |

#### Wireframe

```
+-------------------------------+
|  [BRAND LOGO]  Platform Name  |
|-------------------------------|
|  [separator]                  |
|                               |
|  [chart-bar]    Dashboard     |
|                               |
|  PRODUCTS                     |
|  [cube]         Products      |
|  [rect-stack]   Collections   |
|  [archive-box]  Inventory     |
|                               |
|  [separator]                  |
|  ORDERS                       |
|  [shopping-bag] Orders        |
|                               |
|  [separator]                  |
|  CUSTOMERS                    |
|  [users]        Customers     |
|                               |
|  [separator]                  |
|  DISCOUNTS                    |
|  [tag]          Discounts     |
|                               |
|  [separator]                  |
|  CONTENT                      |
|  [doc-text]     Pages         |
|  [bars-3]       Navigation    |
|  [paint-brush]  Themes        |
|                               |
|  [separator]                  |
|  [chart-pie]    Analytics     |
|  [cog-6-tooth]  Settings      |
|  [squares-2x2]  Apps          |
|  [code-bracket] Developers    |
+-------------------------------+
```

#### Navigation Structure

```
Dashboard                        -> admin.dashboard
Products (group label)
  Products                       -> admin.products.index
  Collections                    -> admin.collections.index
  Inventory                      -> admin.inventory.index
Orders
  Orders                         -> admin.orders.index
Customers
  Customers                      -> admin.customers.index
Discounts
  Discounts                      -> admin.discounts.index
Content (group label)
  Pages                          -> admin.pages.index
  Navigation                     -> admin.navigation.index
  Themes                         -> admin.themes.index
Analytics                        -> admin.analytics.index
Settings                         -> admin.settings.index
Apps                             -> admin.apps.index
Developers                       -> admin.developers.index
```

Each nav item displays an icon (via Flux icon component) and a text label. The active item receives a highlighted background and bold text. All navigation links use Livewire SPA-style page transitions. Groups are visually separated with Flux separator components.

#### Flux Component Mapping

| UI Element | Flux Component |
|-----------|----------------|
| Platform logo and name | `brand` |
| Navigation icons | `icon` |
| Group dividers | `separator` |

#### Responsive Behavior

- **Desktop (lg and above):** Sidebar is always visible, fixed left, 256px wide. Main content has a left margin of 256px.
- **Mobile (below lg):** Sidebar is hidden off-screen. A hamburger button (icon: bars-3) appears in the top bar. When user clicks it, the sidebar slides in as an overlay with a semi-transparent backdrop. Clicking the backdrop or a nav item closes it.

### 1.3 Top Bar

**Livewire component:** `App\Livewire\Admin\Layout\TopBar`

**Blade view:** `resources/views/livewire/admin/layout/top-bar.blade.php`

#### Component Requirements

| Property | Type | Purpose |
|----------|------|---------|
| `currentStoreName` | `string` | Name of the active store |
| `stores` | `Collection` | List of stores the user has access to |
| `unreadNotificationCount` | `int` | Count of unread notifications |

| Action | Behavior |
|--------|----------|
| `switchStore(string $storeId…965 chars truncated…ng

| UI Element | Flux Component |
|-----------|----------------|
| Store selector | `dropdown` |
| Hamburger (mobile) | `button` (icon: bars-3) |
| Notification bell | `button` (icon: bell, variant: ghost) |
| Notification count | `badge` |
| User profile trigger | `profile` |
| Profile menu | `dropdown` |
| Menu divider | `separator` |

### 1.4 Main Content Area

The main content area sits to the right of the sidebar and below the top bar. Every page renders within this area.

**Breadcrumbs:** Every page includes a Flux breadcrumbs component at the top of the content area. The first item is always "Home" linking to `/admin`. Subsequent items reflect the page hierarchy. Example for product edit: Home > Products > "Blue T-Shirt".

**Layout wrapper:** The content area has consistent padding (larger on desktop, smaller on mobile) and a max-width container centered horizontally.

### 1.5 Toast Notifications

A global toast notification system lives in the layout shell. It uses Alpine.js to listen for Livewire dispatched events.

| Property | Value |
|----------|-------|
| Event name | `toast` |
| Payload | `{ type: 'success' / 'error' / 'info', message: string }` |
| Position | Top-right corner |
| Auto-dismiss | 5 seconds |
| Stacking | Supports multiple toasts |
| Styling | Rounded card, shadow, colored left border (green=success, red=error, blue=info) |

Components dispatch toasts after successful actions (e.g., "Product saved successfully.") or on errors ("Something went wrong. Please try again.").

### 1.6 Dark Mode

The admin supports dark mode via Tailwind's `dark:` variant. The mode follows the user's system preference by default. A toggle can be added in the profile dropdown later. All components and custom markup must include dark mode variants.

Dark mode preference is persisted in `localStorage` under the key `theme` with values `light` or `dark`. On page load, the stored preference is applied before first paint to avoid flash of incorrect theme. If no preference is stored, the system preference (`prefers-color-scheme`) is used as default.

---

## 2. Dashboard

**Route:** `GET /admin`

**Livewire component:** `App\Livewire\Admin\Dashboard`

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

### Component Requirements

| Property | Type | Purpose |
|----------|------|---------|
| `dateRange` | `string` | Active date range preset: `today`, `last_7_days`, `last_30_days`, `custom` |
| `customStartDate` | `?string` | Custom range start (Y-m-d) |
| `customEndDate` | `?string` | Custom range end (Y-m-d) |
| `totalSales` | `int` | Total sales amount in minor units |
| `ordersCount` | `int` | Number of orders in range |
| `averageOrderValue` | `int` | Average order value in minor units |
| `visitorsCount` | `int` | Unique visitors in range |
| `salesChange` | `float` | Percentage change vs previous period |
| `ordersChange` | `float` | Percentage change vs previous period |
| `aovChange` | `float` | Percentage change vs previous period |
| `visitorsChange` | `float` | Percentage change vs previous period |
| `ordersChartData` | `array` | Array of `{date, count}` for last 30 days |
| `topProducts` | `array` | Top 5 products: `{title, units_sold, revenue}` |
| `funnelData` | `array` | Funnel steps: `{visits, add_to_cart, checkout_started, checkout_completed}` |

| Action | Behavior |
|--------|----------|
| `updatedDateRange()` | Re-fetches all KPI data for the selected range |
| `loadKpis()` | Queries aggregated order/analytics data |
| `loadChart()` | Queries daily order counts for chart |
| `loadTopProducts()` | Queries top 5 products by revenue |
| `loadFunnel()` | Queries conversion funnel data |

| Computed Property | Purpose |
|-------------------|---------|
| `formattedTotalSales` | Formatted currency string |
| `formattedAov` | Formatted currency string |

### Wireframe

```
+------------------------------------------------------------------+
| Dashboard                           [Today v] (date range picker) |
+------------------------------------------------------------------+
|                                                                    |
|  +-------------+  +-------------+  +-------------+  +----------+  |
|  | Total Sales |  | Orders      |  | Avg Order   |  | Visitors |  |
|  | $12,345     |  | 156         |  | $79.13      |  | 2,340    |  |
|  | [+12.3%] ^  |  | [+8.1%] ^  |  | [-2.1%] v  |  | [+5%] ^  |  |
|  +-------------+  +-------------+  +-------------+  +----------+  |
|                                                                    |
|  +--------------------------------------------------------------+  |
|  | Orders over time                                              |  |
|  |  [line/bar chart - daily order counts for selected range]     |  |
|  +--------------------------------------------------------------+  |
|                                                                    |
|  +------------------------------+  +----------------------------+  |


/bin/zsh
-lc
sed -n '1,260p' specs/02-API-ROUTES.md

# API and Routing Specification

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.

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).

---

## 1. Web Routes (Blade/Livewire Pages)

### 1.1 Admin Auth Routes

No middleware beyond the default `web` group. These routes handle admin login and logout.

| Method | Path | Description | Component / Action |
|--------|------|-------------|--------------------|
| GET | `/admin/login` | Admin login page | `Livewire: Admin\Auth\Login` |
| POST | `/admin/login` | Process admin login | Handled by Livewire action |
| POST | `/admin/logout` | Logout and invalidate session | Handled by Livewire action |
| GET | `/admin/forgot-password` | Show forgot password form | `Livewire: Admin\Auth\ForgotPassword` |
| POST | `/admin/forgot-password` | Send password reset link | Handled by Livewire action |
| GET | `/admin/reset-password/{token}` | Show reset password form | `Livewire: Admin\Auth\ResetPassword` |
| POST | `/admin/reset-password` | Reset password with token | Handled by Livewire action |

Rate limit: `login` tier (5 per minute per IP).

---

### 1.2 Admin Routes

**Prefix:** `/admin`
**Middleware:** `web`, `auth`, `verified`, `store.resolve`, `role.check`

All admin pages render Livewire full-page components. Data mutations happen via Livewire actions (not separate POST endpoints).

> **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.

#### Dashboard

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/admin` | Dashboard with KPI tiles, orders chart, conversion funnel | `Admin\Dashboard` |

#### Products

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/admin/products` | Product list - searchable, filterable by status, bulk actions | `Admin\Products\Index` |
| GET | `/admin/products/create` | Create product form | `Admin\Products\Create` |
| GET | `/admin/products/{product}/edit` | Edit product form with variants, media, options | `Admin\Products\Edit` |

#### Inventory

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/admin/inventory` | Inventory management page | `Admin\Inventory\Index` |

#### Collections

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/admin/collections` | Collection list | `Admin\Collections\Index` |
| GET | `/admin/collections/create` | Create collection form | `Admin\Collections\Create` |
| GET | `/admin/collections/{collection}/edit` | Edit collection form | `Admin\Collections\Edit` |

#### Orders

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/admin/orders` | Order list with status filters | `Admin\Orders\Index` |
| GET | `/admin/orders/{order}` | Order detail with timeline, payments, fulfillments | `Admin\Orders\Show` |

#### Customers

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/admin/customers` | Customer list | `Admin\Customers\Index` |
| GET | `/admin/customers/{customer}` | Customer detail with order history, addresses | `Admin\Customers\Show` |

#### Discounts

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/admin/discounts` | Discount list | `Admin\Discounts\Index` |
| GET | `/admin/discounts/create` | Create discount form with rules builder | `Admin\Discounts\Create` |
| GET | `/admin/discounts/{discount}/edit` | Edit discount form | `Admin\Discounts\Edit` |

#### Settings

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/admin/settings` | Store settings (tabs: General, Domains, Shipping, Taxes, Checkout, Notifications) | `Admin\Settings\Index` |
| GET | `/admin/settings/shipping` | Shipping zones and rates configuration | `Admin\Settings\Shipping` |
| GET | `/admin/settings/taxes` | Tax settings - mode, provider config, rates | `Admin\Settings\Taxes` |

#### Themes and Online Store

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/admin/themes` | Theme management - cards with actions (Customize, Preview, Publish, Duplicate, Delete) | `Admin\Themes\Index` |
| GET | `/admin/themes/{theme}/editor` | Theme editor - left panel sections, center preview iframe, right panel settings | `Admin\Themes\Editor` |

#### Pages

| Method | Path | Description | Component |
|--------|------|--…588 chars truncated…nstalled and available apps | `Admin\Apps\Index` |
| GET | `/admin/apps/{installation}` | Installed app detail - scopes, webhooks, usage | `Admin\Apps\Show` |

#### Developers

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/admin/developers` | Developer settings - API keys/tokens, webhook management | `Admin\Developers\Index` |

> **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.

#### Analytics

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/admin/analytics` | Analytics dashboard - sales, traffic, funnel, exports | `Admin\Analytics\Index` |

#### Search Settings

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/admin/search/settings` | Search settings - synonyms, stopwords, reindex | `Admin\Search\Settings` |

---

### 1.3 Storefront Routes

**Middleware:** `web`, `store.resolve`

These routes serve the public-facing storefront using Blade templates rendered through the active theme engine.

| Method | Path | Description | Component / View |
|--------|------|-------------|------------------|
| GET | `/` | Home page | `Storefront\Home` |
| GET | `/collections` | All collections listing | `Storefront\Collections\Index` |
| GET | `/collections/{handle}` | Single collection page with product grid and filters | `Storefront\Collections\Show` |
| GET | `/products/{handle}` | Product page - gallery, variant selector, add-to-cart | `Storefront\Products\Show` |
| GET | `/cart` | Full cart page - line items, quantity controls, discount input, totals | `Storefront\Cart\Show` |
| GET | `/search` | Search results page with filter panel, sort dropdown, product grid | `Storefront\Search\Index` |
| GET | `/pages/{handle}` | Static content page (About, FAQ, etc.) | `Storefront\Pages\Show` |

#### Customer Account Routes

**Additional middleware:** `auth:customer`

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/account` | Customer account overview | `Storefront\Account\Dashboard` |
| GET | `/account/orders` | Order history list | `Storefront\Account\Orders\Index` |
| GET | `/account/orders/{orderNumber}` | Order detail | `Storefront\Account\Orders\Show` |
| GET | `/account/addresses` | Address book management | `Storefront\Account\Addresses\Index` |

#### Customer Auth Routes (no auth required)

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/account/login` | Customer login page | `Storefront\Account\Auth\Login` |
| GET | `/account/register` | Customer registration page | `Storefront\Account\Auth\Register` |
| GET | `/forgot-password` | Forgot password page | `Storefront\Account\Auth\ForgotPassword` |
| POST | `/forgot-password` | Send reset link | Handled by Livewire action |
| GET | `/reset-password/{token}` | Reset password form | `Storefront\Account\Auth\ResetPassword` |
| POST | `/reset-password` | Reset password | Handled by Livewire action |

---

### 1.4 Checkout Routes

**Middleware:** `web`, `store.resolve`

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/checkout/{checkoutId}` | Checkout page - stepper UI (Contact -> Shipping -> Payment) | `Storefront\Checkout\Show` |
| GET | `/checkout/{checkoutId}/confirmation` | Order confirmation page - order number, items, totals | `Storefront\Checkout\Confirmation` |

---

## 2. Storefront REST API

**Prefix:** `/api/storefront/v1`
**Middleware:** `store.resolve`, `throttle:api.storefront`
**Content-Type:** `application/json`

These endpoints power the interactive storefront experience (cart drawer, checkout flow, search autocomplete, analytics).

---

### 2.1 Cart Endpoints

#### POST /api/storefront/v1/carts

Create a new cart.

- **Authentication:** None (guest) or session-based (logged-in customer)
- **Rate limit:** `api.storefront` (120/min per IP)

**Request body:**

```json
{
  "currency": "EUR"
}
```

| Field | Type | Required | Validation |
|-------|------|----------|------------|
| `currency` | string | No | Valid ISO 4217 code. Defaults to store's `default_currency`. |

**Success response:** `201 Created`

```json
{
  "id": 42,
  "store_id": 1,
  "customer_id": null,
  "currency": "EUR",
  "cart_version": 1,
  "status": "active",
  "lines": [],
  "totals": {
    "subtotal": 0,
    "discount": 0,
    "total": 0,
    "currency": "EUR"
  },
  "created_at": "2026-02-11T10:00:00Z",
  "updated_at": "2026-02-11T10:00:00Z"
}
```

**Error responses:**
- `422` - Validation error (invalid currency)

---

#### GET /api/storefront/v1/carts/{cartId}

Retrieve a cart with all lines and computed totals.

- **Authentication:** None (cart ID acts as token)
- **Rate limit:** `api.storefront` (120/min per IP)


sed -n '1,260p' specs/01-DATABASE-SCHEMA.md

# 01 - Database Schema Specification

> E-commerce platform - SQLite database schema for Laravel 12

---

## Conventions

| Convention | Detail |
|---|---|
| Primary keys | Auto-incrementing integer primary key on every table (column: `id`) |
| Tenant isolation | `store_id` foreign key on every tenant-scoped table |
| Monetary amounts | Stored as **integer in minor units** (cents). Column names end in `_amount`. |
| Booleans | Integer, not null, default 0 (0 = false, 1 = true) |
| JSON data | Text column - Laravel casts to/from JSON at the application layer |
| Enumerations | Check constraints on text columns |
| Timestamps | Text (ISO-8601) via Laravel's `created_at` / `updated_at` |
| Soft deletes | Only where explicitly noted (`deleted_at` text, nullable) |
| Encrypted columns | Suffixed `_encrypted` - encrypted at application layer, stored as text |

---

## Epic 1 - Foundation and Multi-Store Tenancy

### organizations

Billing entity that owns one or more stores.

| Column | Type | Nullable | Default | Constraints | Notes |
|---|---|---|---|---|---|
| id | INTEGER | No | autoincrement | PRIMARY KEY | |
| name | TEXT | No | - | - | Organization display name |
| billing_email | TEXT | No | - | - | Primary billing contact |
| created_at | TEXT | Yes | NULL | - | ISO-8601 timestamp |
| updated_at | TEXT | Yes | NULL | - | ISO-8601 timestamp |

**Indexes:**

- `idx_organizations_billing_email` on (`billing_email`) - Supports lookup by billing email

---

### stores

Individual storefront belonging to an organization.

| Column | Type | Nullable | Default | Constraints | Notes |
|---|---|---|---|---|---|
| id | INTEGER | No | autoincrement | PRIMARY KEY | |
| organization_id | INTEGER | No | - | FK -> organizations(id) ON DELETE CASCADE | Parent organization |
| name | TEXT | No | - | - | Store display name |
| handle | TEXT | No | - | UNIQUE per table | URL-friendly slug |
| status | TEXT | No | 'active' | CHECK IN ('active', 'suspended') | |
| default_currency | TEXT | No | 'USD' | - | ISO 4217 currency code |
| default_locale | TEXT | No | 'en' | - | IETF locale tag |
| timezone | TEXT | No | 'UTC' | - | IANA timezone |
| created_at | TEXT | Yes | NULL | - | ISO-8601 timestamp |
| updated_at | TEXT | Yes | NULL | - | ISO-8601 timestamp |

**Indexes:**

- `idx_stores_handle` UNIQUE on (`handle`) - Ensures globally unique store handles for URL routing
- `idx_stores_organization_id` on (`organization_id`) - FK lookup and listing stores by org
- `idx_stores_status` on (`status`) - Filtering stores by active/suspended status

---

### store_domains

Custom domains attached to a store.

| Column | Type | Nullable | Default | Constraints | Notes |
|---|---|---|---|---|---|
| id | INTEGER | No | autoincrement | PRIMARY KEY | |
| store_id | INTEGER | No | - | FK -> stores(id) ON DELETE CASCADE | Owning store |
| hostname | TEXT | No | - | UNIQUE per table | e.g. "shop.example.com" |
| type | TEXT | No | 'storefront' | CHECK IN ('storefront', 'admin', 'api') | Domain purpose |
| is_primary | INTEGER | No | 0 | - | Boolean: primary domain flag |
| tls_mode | TEXT | No | 'managed' | CHECK IN ('managed', 'bring_your_own') | TLS certificate mode |
| created_at | TEXT | Yes | NULL | - | ISO-8601 timestamp |

**Indexes:**

- `idx_store_domains_hostname` UNIQUE on (`hostname`) - Globally unique hostname for routing
- `idx_store_domains_store_id` on (`store_id`) - FK lookup, listing domains per store
- `idx_store_domains_store_primary` on (`store_id`, `is_primary`) - Quick lookup of a store's primary domain

---

### users

Admin/staff users who manage stores (not storefront customers).

| Column | Type | Nullable | Default | Constraints | Notes |
|---|---|---|---|---|---|
| id | INTEGER | No | autoincrement | PRIMARY KEY | |
| email | TEXT | No | - | UNIQUE per table | Login email |
| password_hash | TEXT | No | - | - | bcrypt/argon2 hash; maps to Laravel's `password` field |
| name | TEXT | No | - | - | Display name |
| status | TEXT | No | 'active' | CHECK IN ('active', 'disabled') | |
| email_verified_at | TEXT | Yes | NULL | - | ISO-8601 timestamp; required for `verified` middleware |
| last_login_at | TEXT | Yes | NULL | - | ISO-8601 timestamp |
| two_factor_secret | TEXT | Yes | NULL | - | TOTP secret |
| two_factor_recovery_codes | TEXT | Yes | NULL | - | JSON array of recovery codes |
| two_factor_confirmed_at | TEXT | Yes | NULL | - | ISO-8601 timestamp |
| remember_token | TEXT | Yes | NULL | - | Laravel remember-me token |
| created_at | TEXT | Yes | NULL | - | ISO-8601 timestamp |
| updated_at | TEXT | Yes | NULL | - | ISO-8601 timestamp |

**Indexes:**

- `idx_users_email` UNIQUE on (`email`) - Enforces unique email, supports login lookup
- `idx_users_status` on (`status`) - Filtering users by active/disabled

**Notes:** The `two_factor_*` columns align with the existing migration `2025_08_14_170933_add_two_factor_columns_to_users_table.php`. The `password_hash` column maps to Laravel's default `password` fi…1344 chars truncated…ct record, scoped to a store.

| Column | Type | Nullable | Default | Constraints | Notes |
|---|---|---|---|---|---|
| id | INTEGER | No | autoincrement | PRIMARY KEY | |
| store_id | INTEGER | No | - | FK -> stores(id) ON DELETE CASCADE | Owning store |
| title | TEXT | No | - | - | Product display title |
| handle | TEXT | No | - | UNIQUE per (store_id, handle) | URL-friendly slug |
| status | TEXT | No | 'draft' | CHECK IN ('draft', 'active', 'archived') | |
| description_html | TEXT | Yes | NULL | - | Rich-text description |
| vendor | TEXT | Yes | NULL | - | Manufacturer/brand |
| product_type | TEXT | Yes | NULL | - | Category classification |
| tags | TEXT | No | '[]' | - | JSON array, e.g. ["summer","sale"] |
| published_at | TEXT | Yes | NULL | - | ISO-8601 timestamp; null = unpublished |
| created_at | TEXT | Yes | NULL | - | ISO-8601 timestamp |
| updated_at | TEXT | Yes | NULL | - | ISO-8601 timestamp |

**Indexes:**

- `idx_products_store_handle` UNIQUE on (`store_id`, `handle`) - Unique handle per store for URL routing
- `idx_products_store_id` on (`store_id`) - FK lookup, listing all products in a store
- `idx_products_store_status` on (`store_id`, `status`) - Filtering products by status within a store
- `idx_products_published_at` on (`store_id`, `published_at`) - Sorting/filtering by publication date
- `idx_products_vendor` on (`store_id`, `vendor`) - Filtering products by vendor
- `idx_products_product_type` on (`store_id`, `product_type`) - Filtering products by type

**Notes:** `tags` is a JSON-encoded array (e.g. `["summer","sale"]`). The `handle` is unique per store, not globally.

---

### product_options

Named option dimensions for a product (e.g. Size, Color).

| Column | Type | Nullable | Default | Constraints | Notes |
|---|---|---|---|---|---|
| id | INTEGER | No | autoincrement | PRIMARY KEY | |
| product_id | INTEGER | No | - | FK -> products(id) ON DELETE CASCADE | Parent product |
| name | TEXT | No | - | - | Option name, e.g. "Size", "Color" |
| position | INTEGER | No | 0 | UNIQUE per (product_id, position) | Display ordering |

**Indexes:**

- `idx_product_options_product_id` on (`product_id`) - FK lookup, listing options per product
- `idx_product_options_product_position` UNIQUE on (`product_id`, `position`) - Enforces unique ordering per product

---

### product_option_values

Individual values within a product option (e.g. "Small", "Medium", "Large").

| Column | Type | Nullable | Default | Constraints | Notes |
|---|---|---|---|---|---|
| id | INTEGER | No | autoincrement | PRIMARY KEY | |
| product_option_id | INTEGER | No | - | FK -> product_options(id) ON DELETE CASCADE | Parent option |
| value | TEXT | No | - | - | Display value, e.g. "Small" |
| position | INTEGER | No | 0 | UNIQUE per (product_option_id, position) | Display ordering |

**Indexes:**

- `idx_product_option_values_option_id` on (`product_option_id`) - FK lookup, listing values per option
- `idx_product_option_values_option_position` UNIQUE on (`product_option_id`, `position`) - Enforces unique ordering per option

---

### product_variants

Purchasable SKU-level variant of a product.

| Column | Type | Nullable | Default | Constraints | Notes |
|---|---|---|---|---|---|
| id | INTEGER | No | autoincrement | PRIMARY KEY | |
| product_id | INTEGER | No | - | FK -> products(id) ON DELETE CASCADE | Parent product |
| sku | TEXT | Yes | NULL | - | Stock keeping unit |
| barcode | TEXT | Yes | NULL | - | UPC/EAN barcode |
| price_amount | INTEGER | No | 0 | - | Price in minor units (cents) |
| compare_at_amount | INTEGER | Yes | NULL | - | Original/compare price in minor units |
| currency | TEXT | No | 'USD' | - | ISO 4217 currency code |
| weight_g | INTEGER | Yes | NULL | - | Weight in grams |
| requires_shipping | INTEGER | No | 1 | - | Boolean: physical product flag |
| is_default | INTEGER | No | 0 | - | Boolean: default variant flag |
| position | INTEGER | No | 0 | - | Display ordering |
| status | TEXT | No | 'active' | CHECK IN ('active', 'archived') | |
| created_at | TEXT | Yes | NULL | - | ISO-8601 timestamp |
| updated_at | TEXT | Yes | NULL | - | ISO-8601 timestamp |

**Indexes:**

- `idx_product_variants_product_id` on (`product_id`) - FK lookup, listing variants per product
- `idx_product_variants_sku` on (`sku`) - SKU lookup across all variants
- `idx_product_variants_barcode` on (`barcode`) - Barcode scanning lookup
- `idx_product_variants_product_position` on (`product_id`, `position`) - Ordered variant listing
- `idx_product_variants_product_default` on (`product_id`, `is_default`) - Quick lookup of default variant

**Notes:** `price_amount` and `compare_at_amount` are in minor currency units (cents). A product always has at least one variant (the default).

---

### variant_option_values

Maps a variant to its chosen option values (e.g. variant #5 = Size:Large + Color:Red). Junction table with composite primary key.

| Column | Type | Nullable | Default | Constraints | Notes |


Output:
Chunk ID: 17fde9
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 2836
Output:
# 01 - Database Schema Specification

> E-commerce platform - SQLite database schema for Laravel 12

---

## Conventions

| Convention | Detail |
|---|---|
| Primary keys | Auto-incrementing integer primary key on every table (column: `id`) |
| Tenant isolation | `store_id` foreign key on every tenant-scoped table |
| Monetary amounts | Stored as **integer in minor units** (cents). Column names end in `_amount`. |
| Booleans | Integer, not null, default 0 (0 = false, 1 = true) |
| JSON data | Text column - Laravel casts to/from JSON at the application layer |
| Enumerations | Check constraints on text columns |
| Timestamps | Text (ISO-8601) via Laravel's `created_at` / `updated_at` |
| Soft deletes | Only where explicitly noted (`deleted_at` text, nullable) |
| Encrypted columns | Suffixed `_encrypted` - encrypted at application layer, stored as text |

---

## Epic 1 - Foundation and Multi-Store Tenancy

### organizations

Billing entity that owns one or more stores.

| Column | Type | Nullable | Default | Constraints | Notes |
|---|---|---|---|---|---|
| id | INTEGER | No | autoincrement | PRIMARY KEY | |
| name | TEXT | No | - | - | Organization display name |
| billing_email | TEXT | No | - | - | Primary billing contact |
| created_at | TEXT | Yes | NULL | - | ISO-8601 timestamp |
| updated_at | TEXT | Yes | NULL | - | ISO-8601 timestamp |

**Indexes:**

- `idx_organizations_billing_email` on (`billing_email`) - Supports lookup by billing email

---

### stores

Individual storefront belonging to an organization.

| Column | Type | Nullable | Default | Constraints | Notes |
|---|---|---|---|---|---|
| id | INTEGER | No | autoincrement | PRIMARY KEY | |
| organization_id | INTEGER | No | - | FK -> organizations(id) ON DELETE CASCADE | Parent organization |
| name | TEXT | No | - | - | Store display name |
| handle | TEXT | No | - | UNIQUE per table | URL-friendly slug |
| status | TEXT | No | 'active' | CHECK IN ('active', 'suspended') | |
| default_currency | TEXT | No | 'USD' | - | ISO 4217 currency code |
| default_locale | TEXT | No | 'en' | - | IETF locale tag |
| timezone | TEXT | No | 'UTC' | - | IANA timezone |
| created_at | TEXT | Yes | NULL | - | ISO-8601 timestamp |
| updated_at | TEXT | Yes | NULL | - | ISO-8601 timestamp |

**Indexes:**

- `idx_stores_handle` UNIQUE on (`handle`) - Ensures globally unique store handles for URL routing
- `idx_stores_organization_id` on (`organization_id`) - FK lookup and listing stores by org
- `idx_stores_status` on (`status`) - Filtering stores by active/suspended status

---

### store_domains

Custom domains attached to a store.

| Column | Type | Nullable | Default | Constraints | Notes |
|---|---|---|---|---|---|
| id | INTEGER | No | autoincrement | PRIMARY KEY | |
| store_id | INTEGER | No | - | FK -> stores(id) ON DELETE CASCADE | Owning store |
| hostname | TEXT | No | - | UNIQUE per table | e.g. "shop.example.com" |
| type | TEXT | No | 'storefront' | CHECK IN ('storefront', 'admin', 'api') | Domain purpose |
| is_primary | INTEGER | No | 0 | - | Boolean: primary domain flag |
| tls_mode | TEXT | No | 'managed' | CHECK IN ('managed', 'bring_your_own') | TLS certificate mode |
| created_at | TEXT | Yes | NULL | - | ISO-8601 timestamp |

**Indexes:**

- `idx_store_domains_hostname` UNIQUE on (`hostname`) - Globally unique hostname for routing
- `idx_store_domains_store_id` on (`store_id`) - FK lookup, listing domains per store
- `idx_store_domains_store_primary` on (`store_id`, `is_primary`) - Quick lookup of a store's primary domain

---

### users

Admin/staff users who manage stores (not storefront customers).

| Column | Type | Nullable | Default | Constraints | Notes |
|---|---|---|---|---|---|
| id | INTEGER | No | autoincrement | PRIMARY KEY | |
| email | TEXT | No | - | UNIQUE per table | Login email |
| password_hash | TEXT | No | - | - | bcrypt/argon2 hash; maps to Laravel's `password` field |
| name | TEXT | No | - | - | Display name |
| status | TEXT | No | 'active' | CHECK IN ('active', 'disabled') | |
| email_verified_at | TEXT | Yes | NULL | - | ISO-8601 timestamp; required for `verified` middleware |
| last_login_at | TEXT | Yes | NULL | - | ISO-8601 timestamp |
| two_factor_secret | TEXT | Yes | NULL | - | TOTP secret |
| two_factor_recovery_codes | TEXT | Yes | NULL | - | JSON array of recovery codes |
| two_factor_confirmed_at | TEXT | Yes | NULL | - | ISO-8601 timestamp |
| remember_token | TEXT | Yes | NULL | - | Laravel remember-me token |
| created_at | TEXT | Yes | NULL | - | ISO-8601 timestamp |
| updated_at | TEXT | Yes | NULL | - | ISO-8601 timestamp |

**Indexes:**

- `idx_users_email` UNIQUE on (`email`) - Enforces unique email, supports login lookup
- `idx_users_status` on (`status`) - Filtering users by active/disabled

**Notes:** The `two_factor_*` columns align with the existing migration `2025_08_14_170933_add_two_factor_columns_to_users_table.php`. The `password_hash` column maps to Laravel's default `password` field internally but stores a bcrypt/argon2 hash.

---

### store_users

Pivot table linking users to stores with a role. Uses composite primary key.

| Column | Type | Nullable | Default | Constraints | Notes |
|---|---|---|---|---|---|
| store_id | INTEGER | No | - | PK (composite), FK -> stores(id) ON DELETE CASCADE | |
| user_id | INTEGER | No | - | PK (composite), FK -> users(id) ON DELETE CASCADE | |
| role | TEXT | No | 'staff' | CHECK IN ('owner', 'admin', 'staff', 'support') | |
| created_at | TEXT | Yes | NULL | - | ISO-8601 timestamp |

**Primary Key:** (`store_id`, `user_id`)

**Indexes:**

- `idx_store_users_user_id` on (`user_id`) - Reverse lookup: find all stores for a user
- `idx_store_users_role` on (`store_id`, `role`) - Listing users by role within a store

---

### store_settings

Single-row-per-store JSON settings bag.

| Column | Type | Nullable | Default | Constraints | Notes |
|---|---|---|---|---|---|
| store_id | INTEGER | No | - | PRIMARY KEY, FK -> stores(id) ON DELETE CASCADE | One-to-one with stores |
| settings_json | TEXT | No | '{}' | - | JSON settings object |
| updated_at | TEXT | Yes | NULL | - | ISO-8601 timestamp |

**Notes:** No additional indexes needed - primary key on `store_id` covers all queries.

---

## Epic 2 - Catalog, Collections, Inventory, and Media

### products

Core product record, scoped to a store.

| Column | Type | Nullable | Default | Constraints | Notes |
|---|---|---|---|---|---|
| id | INTEGER | No | autoincrement | PRIMARY KEY | |
| store_id | INTEGER | No | - | FK -> stores(id) ON DELETE CASCADE | Owning store |
| title | TEXT | No | - | - | Product display title |
| handle | TEXT | No | - | UNIQUE per (store_id, handle) | URL-friendly slug |
| status | TEXT | No | 'draft' | CHECK IN ('draft', 'active', 'archived') | |
| description_html | TEXT | Yes | NULL | - | Rich-text description |
| vendor | TEXT | Yes | NULL | - | Manufacturer/brand |
| product_type | TEXT | Yes | NULL | - | Category classification |
| tags | TEXT | No | '[]' | - | JSON array, e.g. ["summer","sale"] |
| published_at | TEXT | Yes | NULL | - | ISO-8601 timestamp; null = unpublished |
| created_at | TEXT | Yes | NULL | - | ISO-8601 timestamp |
| updated_at | TEXT | Yes | NULL | - | ISO-8601 timestamp |

**Indexes:**

- `idx_products_store_handle` UNIQUE on (`store_id`, `handle`) - Unique handle per store for URL routing
- `idx_products_store_id` on (`store_id`) - FK lookup, listing all products in a store
- `idx_products_store_status` on (`store_id`, `status`) - Filtering products by status within a store
- `idx_products_published_at` on (`store_id`, `published_at`) - Sorting/filtering by publication date
- `idx_products_vendor` on (`store_id`, `vendor`) - Filtering products by vendor
- `idx_products_product_type` on (`store_id`, `product_type`) - Filtering products by type

**Notes:** `tags` is a JSON-encoded array (e.g. `["summer","sale"]`). The `handle` is unique per store, not globally.

---

### product_options

Named option dimensions for a product (e.g. Size, Color).

| Column | Type | Nullable | Default | Constraints | Notes |
|---|---|---|---|---|---|
| id | INTEGER | No | autoincrement | PRIMARY KEY | |
| product_id | INTEGER | No | - | FK -> products(id) ON DELETE CASCADE | Parent product |
| name | TEXT | No | - | - | Option name, e.g. "Size", "Color" |
| position | INTEGER | No | 0 | UNIQUE per (product_id, position) | Display ordering |

**Indexes:**

- `idx_product_options_product_id` on (`product_id`) - FK lookup, listing options per product
- `idx_product_options_product_position` UNIQUE on (`product_id`, `position`) - Enforces unique ordering per product

---

### product_option_values

Individual values within a product option (e.g. "Small", "Medium", "Large").

| Column | Type | Nullable | Default | Constraints | Notes |
|---|---|---|---|---|---|
| id | INTEGER | No | autoincrement | PRIMARY KEY | |
| product_option_id | INTEGER | No | - | FK -> product_options(id) ON DELETE CASCADE | Parent option |
| value | TEXT | No | - | - | Display value, e.g. "Small" |
| position | INTEGER | No | 0 | UNIQUE per (product_option_id, position) | Display ordering |

**Indexes:**

- `idx_product_option_values_option_id` on (`product_option_id`) - FK lookup, listing values per option
- `idx_product_option_values_option_position` UNIQUE on (`product_option_id`, `position`) - Enforces unique ordering per option

---

### product_variants

Purchasable SKU-level variant of a product.

| Column | Type | Nullable | Default | Constraints | Notes |
|---|---|---|---|---|---|
| id | INTEGER | No | autoincrement | PRIMARY KEY | |
| product_id | INTEGER | No | - | FK -> products(id) ON DELETE CASCADE | Parent product |
| sku | TEXT | Yes | NULL | - | Stock keeping unit |
| barcode | TEXT | Yes | NULL | - | UPC/EAN barcode |
| price_amount | INTEGER | No | 0 | - | Price in minor units (cents) |
| compare_at_amount | INTEGER | Yes | NULL | - | Original/compare price in minor units |
| currency | TEXT | No | 'USD' | - | ISO 4217 currency code |
| weight_g | INTEGER | Yes | NULL | - | Weight in grams |
| requires_shipping | INTEGER | No | 1 | - | Boolean: physical product flag |
| is_default | INTEGER | No | 0 | - | Boolean: default variant flag |
| position | INTEGER | No | 0 | - | Display ordering |
| status | TEXT | No | 'active' | CHECK IN ('active', 'archived') | |
| created_at | TEXT | Yes | NULL | - | ISO-8601 timestamp |
| updated_at | TEXT | Yes | NULL | - | ISO-8601 timestamp |

**Indexes:**

- `idx_product_variants_product_id` on (`product_id`) - FK lookup, listing variants per product
- `idx_product_variants_sku` on (`sku`) - SKU lookup across all variants
- `idx_product_variants_barcode` on (`barcode`) - Barcode scanning lookup
- `idx_product_variants_product_position` on (`product_id`, `position`) - Ordered variant listing
- `idx_product_variants_product_default` on (`product_id`, `is_default`) - Quick lookup of default variant

**Notes:** `price_amount` and `compare_at_amount` are in minor currency units (cents). A product always has at least one variant (the default).

---

### variant_option_values

Maps a variant to its chosen option values (e.g. variant #5 = Size:Large + Color:Red). Junction table with composite primary key.

| Column | Type | Nullable | Default | Constraints | Notes |


Output:
Chunk ID: 09dcbe
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 2647
Output:
# API and Routing Specification

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.

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).

---

## 1. Web Routes (Blade/Livewire Pages)

### 1.1 Admin Auth Routes

No middleware beyond the default `web` group. These routes handle admin login and logout.

| Method | Path | Description | Component / Action |
|--------|------|-------------|--------------------|
| GET | `/admin/login` | Admin login page | `Livewire: Admin\Auth\Login` |
| POST | `/admin/login` | Process admin login | Handled by Livewire action |
| POST | `/admin/logout` | Logout and invalidate session | Handled by Livewire action |
| GET | `/admin/forgot-password` | Show forgot password form | `Livewire: Admin\Auth\ForgotPassword` |
| POST | `/admin/forgot-password` | Send password reset link | Handled by Livewire action |
| GET | `/admin/reset-password/{token}` | Show reset password form | `Livewire: Admin\Auth\ResetPassword` |
| POST | `/admin/reset-password` | Reset password with token | Handled by Livewire action |

Rate limit: `login` tier (5 per minute per IP).

---

### 1.2 Admin Routes

**Prefix:** `/admin`
**Middleware:** `web`, `auth`, `verified`, `store.resolve`, `role.check`

All admin pages render Livewire full-page components. Data mutations happen via Livewire actions (not separate POST endpoints).

> **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.

#### Dashboard

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/admin` | Dashboard with KPI tiles, orders chart, conversion funnel | `Admin\Dashboard` |

#### Products

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/admin/products` | Product list - searchable, filterable by status, bulk actions | `Admin\Products\Index` |
| GET | `/admin/products/create` | Create product form | `Admin\Products\Create` |
| GET | `/admin/products/{product}/edit` | Edit product form with variants, media, options | `Admin\Products\Edit` |

#### Inventory

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/admin/inventory` | Inventory management page | `Admin\Inventory\Index` |

#### Collections

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/admin/collections` | Collection list | `Admin\Collections\Index` |
| GET | `/admin/collections/create` | Create collection form | `Admin\Collections\Create` |
| GET | `/admin/collections/{collection}/edit` | Edit collection form | `Admin\Collections\Edit` |

#### Orders

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/admin/orders` | Order list with status filters | `Admin\Orders\Index` |
| GET | `/admin/orders/{order}` | Order detail with timeline, payments, fulfillments | `Admin\Orders\Show` |

#### Customers

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/admin/customers` | Customer list | `Admin\Customers\Index` |
| GET | `/admin/customers/{customer}` | Customer detail with order history, addresses | `Admin\Customers\Show` |

#### Discounts

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/admin/discounts` | Discount list | `Admin\Discounts\Index` |
| GET | `/admin/discounts/create` | Create discount form with rules builder | `Admin\Discounts\Create` |
| GET | `/admin/discounts/{discount}/edit` | Edit discount form | `Admin\Discounts\Edit` |

#### Settings

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/admin/settings` | Store settings (tabs: General, Domains, Shipping, Taxes, Checkout, Notifications) | `Admin\Settings\Index` |
| GET | `/admin/settings/shipping` | Shipping zones and rates configuration | `Admin\Settings\Shipping` |
| GET | `/admin/settings/taxes` | Tax settings - mode, provider config, rates | `Admin\Settings\Taxes` |

#### Themes and Online Store

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/admin/themes` | Theme management - cards with actions (Customize, Preview, Publish, Duplicate, Delete) | `Admin\Themes\Index` |
| GET | `/admin/themes/{theme}/editor` | Theme editor - left panel sections, center preview iframe, right panel settings | `Admin\Themes\Editor` |

#### Pages

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/admin/pages` | Content pages list | `Admin\Pages\Index` |
| GET | `/admin/pages/create` | Create content page | `Admin\Pages\Create` |
| GET | `/admin/pages/{page}/edit` | Edit content page | `Admin\Pages\Edit` |

#### Navigation

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/admin/navigation` | Navigation menus management | `Admin\Navigation\Index` |

#### Apps

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/admin/apps` | Apps directory - list installed and available apps | `Admin\Apps\Index` |
| GET | `/admin/apps/{installation}` | Installed app detail - scopes, webhooks, usage | `Admin\Apps\Show` |

#### Developers

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/admin/developers` | Developer settings - API keys/tokens, webhook management | `Admin\Developers\Index` |

> **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.

#### Analytics

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/admin/analytics` | Analytics dashboard - sales, traffic, funnel, exports | `Admin\Analytics\Index` |

#### Search Settings

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/admin/search/settings` | Search settings - synonyms, stopwords, reindex | `Admin\Search\Settings` |

---

### 1.3 Storefront Routes

**Middleware:** `web`, `store.resolve`

These routes serve the public-facing storefront using Blade templates rendered through the active theme engine.

| Method | Path | Description | Component / View |
|--------|------|-------------|------------------|
| GET | `/` | Home page | `Storefront\Home` |
| GET | `/collections` | All collections listing | `Storefront\Collections\Index` |
| GET | `/collections/{handle}` | Single collection page with product grid and filters | `Storefront\Collections\Show` |
| GET | `/products/{handle}` | Product page - gallery, variant selector, add-to-cart | `Storefront\Products\Show` |
| GET | `/cart` | Full cart page - line items, quantity controls, discount input, totals | `Storefront\Cart\Show` |
| GET | `/search` | Search results page with filter panel, sort dropdown, product grid | `Storefront\Search\Index` |
| GET | `/pages/{handle}` | Static content page (About, FAQ, etc.) | `Storefront\Pages\Show` |

#### Customer Account Routes

**Additional middleware:** `auth:customer`

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/account` | Customer account overview | `Storefront\Account\Dashboard` |
| GET | `/account/orders` | Order history list | `Storefront\Account\Orders\Index` |
| GET | `/account/orders/{orderNumber}` | Order detail | `Storefront\Account\Orders\Show` |
| GET | `/account/addresses` | Address book management | `Storefront\Account\Addresses\Index` |

#### Customer Auth Routes (no auth required)

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/account/login` | Customer login page | `Storefront\Account\Auth\Login` |
| GET | `/account/register` | Customer registration page | `Storefront\Account\Auth\Register` |
| GET | `/forgot-password` | Forgot password page | `Storefront\Account\Auth\ForgotPassword` |
| POST | `/forgot-password` | Send reset link | Handled by Livewire action |
| GET | `/reset-password/{token}` | Reset password form | `Storefront\Account\Auth\ResetPassword` |
| POST | `/reset-password` | Reset password | Handled by Livewire action |

---

### 1.4 Checkout Routes

**Middleware:** `web`, `store.resolve`

| Method | Path | Description | Component |
|--------|------|-------------|-----------|
| GET | `/checkout/{checkoutId}` | Checkout page - stepper UI (Contact -> Shipping -> Payment) | `Storefront\Checkout\Show` |
| GET | `/checkout/{checkoutId}/confirmation` | Order confirmation page - order number, items, totals | `Storefront\Checkout\Confirmation` |

---

## 2. Storefront REST API

**Prefix:** `/api/storefront/v1`
**Middleware:** `store.resolve`, `throttle:api.storefront`
**Content-Type:** `application/json`

These endpoints power the interactive storefront experience (cart drawer, checkout flow, search autocomplete, analytics).

---

### 2.1 Cart Endpoints

#### POST /api/storefront/v1/carts

Create a new cart.

- **Authentication:** None (guest) or session-based (logged-in customer)
- **Rate limit:** `api.storefront` (120/min per IP)

**Request body:**

```json
{
  "currency": "EUR"
}
```

| Field | Type | Required | Validation |
|-------|------|----------|------------|
| `currency` | string | No | Valid ISO 4217 code. Defaults to store's `default_currency`. |

**Success response:** `201 Created`

```json
{
  "id": 42,
  "store_id": 1,
  "customer_id": null,
  "currency": "EUR",
  "cart_version": 1,
  "status": "active",
  "lines": [],
  "totals": {
    "subtotal": 0,
    "discount": 0,
    "total": 0,
    "currency": "EUR"
  },
  "created_at": "2026-02-11T10:00:00Z",
  "updated_at": "2026-02-11T10:00:00Z"
}
```

**Error responses:**
- `422` - Validation error (invalid currency)

---

#### GET /api/storefront/v1/carts/{cartId}

Retrieve a cart with all lines and computed totals.

- **Authentication:** None (cart ID acts as token)
- **Rate limit:** `api.storefront` (120/min per IP)


Output:
Chunk ID: 616d7e
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 2742
Output:
# 03 - Admin Panel UI/UX Specification

This document defines every view, component, and interaction for the admin panel. The admin is entirely server-rendered using Laravel 12 + Livewire v4 + Flux UI Free components + Tailwind CSS v4. There is no SPA or client-side router.

**Flux UI Free components available:** avatar, badge, brand, breadcrumbs, button, callout, checkbox, dropdown, field, heading, icon, input, modal, navbar, profile, radio, select, separator, switch, text, textarea, tooltip

Anything not covered by Flux UI Free uses plain Tailwind CSS v4 markup with Livewire directives.

---

## 1. Admin Layout Shell

The admin layout is a persistent shell that wraps every admin page. It consists of a left sidebar, a top bar, and a main content area.

### 1.1 Route Prefix

All admin routes live under `/admin` and are protected by authentication middleware plus store-scoping middleware. The store is resolved from the session or a store-selector in the top bar.

### 1.2 Sidebar Navigation

**Livewire component:** `App\Livewire\Admin\Layout\Sidebar`

**Blade view:** `resources/views/livewire/admin/layout/sidebar.blade.php`

#### Component Requirements

| Property | Type | Purpose |
|----------|------|---------|
| `collapsed` | `bool` | Whether the sidebar is collapsed (mobile state) |
| `currentRoute` | `string` | The current route name for active highlighting |

| Action | Behavior |
|--------|----------|
| `toggle()` | Toggles the `collapsed` state |

#### Wireframe

```
+-------------------------------+
|  [BRAND LOGO]  Platform Name  |
|-------------------------------|
|  [separator]                  |
|                               |
|  [chart-bar]    Dashboard     |
|                               |
|  PRODUCTS                     |
|  [cube]         Products      |
|  [rect-stack]   Collections   |
|  [archive-box]  Inventory     |
|                               |
|  [separator]                  |
|  ORDERS                       |
|  [shopping-bag] Orders        |
|                               |
|  [separator]                  |
|  CUSTOMERS                    |
|  [users]        Customers     |
|                               |
|  [separator]                  |
|  DISCOUNTS                    |
|  [tag]          Discounts     |
|                               |
|  [separator]                  |
|  CONTENT                      |
|  [doc-text]     Pages         |
|  [bars-3]       Navigation    |
|  [paint-brush]  Themes        |
|                               |
|  [separator]                  |
|  [chart-pie]    Analytics     |
|  [cog-6-tooth]  Settings      |
|  [squares-2x2]  Apps          |
|  [code-bracket] Developers    |
+-------------------------------+
```

#### Navigation Structure

```
Dashboard                        -> admin.dashboard
Products (group label)
  Products                       -> admin.products.index
  Collections                    -> admin.collections.index
  Inventory                      -> admin.inventory.index
Orders
  Orders                         -> admin.orders.index
Customers
  Customers                      -> admin.customers.index
Discounts
  Discounts                      -> admin.discounts.index
Content (group label)
  Pages                          -> admin.pages.index
  Navigation                     -> admin.navigation.index
  Themes                         -> admin.themes.index
Analytics                        -> admin.analytics.index
Settings                         -> admin.settings.index
Apps                             -> admin.apps.index
Developers                       -> admin.developers.index
```

Each nav item displays an icon (via Flux icon component) and a text label. The active item receives a highlighted background and bold text. All navigation links use Livewire SPA-style page transitions. Groups are visually separated with Flux separator components.

#### Flux Component Mapping

| UI Element | Flux Component |
|-----------|----------------|
| Platform logo and name | `brand` |
| Navigation icons | `icon` |
| Group dividers | `separator` |

#### Responsive Behavior

- **Desktop (lg and above):** Sidebar is always visible, fixed left, 256px wide. Main content has a left margin of 256px.
- **Mobile (below lg):** Sidebar is hidden off-screen. A hamburger button (icon: bars-3) appears in the top bar. When user clicks it, the sidebar slides in as an overlay with a semi-transparent backdrop. Clicking the backdrop or a nav item closes it.

### 1.3 Top Bar

**Livewire component:** `App\Livewire\Admin\Layout\TopBar`

**Blade view:** `resources/views/livewire/admin/layout/top-bar.blade.php`

#### Component Requirements

| Property | Type | Purpose |
|----------|------|---------|
| `currentStoreName` | `string` | Name of the active store |
| `stores` | `Collection` | List of stores the user has access to |
| `unreadNotificationCount` | `int` | Count of unread notifications |

| Action | Behavior |
|--------|----------|
| `switchStore(string $storeId)` | Switches active store in session, redirects to dashboard |

#### Wireframe

```
+--------------------------------------------------------------------+
| [hamburger*]  [Store Name v]                  [bell(3)]  [avatar v] |
+--------------------------------------------------------------------+
  * hamburger visible on mobile only

  Store Name dropdown:
    - Store A
    - Store B
    - Store C

  Avatar dropdown:
    - Settings (links to /admin/settings)
    - [separator]
    - Log out (POST /logout with CSRF)
```

#### Interaction Flows

- When user clicks the store name, a dropdown appears listing all stores. Clicking a store calls `switchStore()` which updates the session and redirects to the dashboard.
- Notification bell shows an unread count badge (positioned absolutely over the icon) when `unreadNotificationCount > 0`.
- Profile dropdown is triggered by a Flux profile component showing the user's avatar and name.

#### Flux Component Mapping

| UI Element | Flux Component |
|-----------|----------------|
| Store selector | `dropdown` |
| Hamburger (mobile) | `button` (icon: bars-3) |
| Notification bell | `button` (icon: bell, variant: ghost) |
| Notification count | `badge` |
| User profile trigger | `profile` |
| Profile menu | `dropdown` |
| Menu divider | `separator` |

### 1.4 Main Content Area

The main content area sits to the right of the sidebar and below the top bar. Every page renders within this area.

**Breadcrumbs:** Every page includes a Flux breadcrumbs component at the top of the content area. The first item is always "Home" linking to `/admin`. Subsequent items reflect the page hierarchy. Example for product edit: Home > Products > "Blue T-Shirt".

**Layout wrapper:** The content area has consistent padding (larger on desktop, smaller on mobile) and a max-width container centered horizontally.

### 1.5 Toast Notifications

A global toast notification system lives in the layout shell. It uses Alpine.js to listen for Livewire dispatched events.

| Property | Value |
|----------|-------|
| Event name | `toast` |
| Payload | `{ type: 'success' / 'error' / 'info', message: string }` |
| Position | Top-right corner |
| Auto-dismiss | 5 seconds |
| Stacking | Supports multiple toasts |
| Styling | Rounded card, shadow, colored left border (green=success, red=error, blue=info) |

Components dispatch toasts after successful actions (e.g., "Product saved successfully.") or on errors ("Something went wrong. Please try again.").

### 1.6 Dark Mode

The admin supports dark mode via Tailwind's `dark:` variant. The mode follows the user's system preference by default. A toggle can be added in the profile dropdown later. All components and custom markup must include dark mode variants.

Dark mode preference is persisted in `localStorage` under the key `theme` with values `light` or `dark`. On page load, the stored preference is applied before first paint to avoid flash of incorrect theme. If no preference is stored, the system preference (`prefers-color-scheme`) is used as default.

---

## 2. Dashboard

**Route:** `GET /admin`

**Livewire component:** `App\Livewire\Admin\Dashboard`

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

### Component Requirements

| Property | Type | Purpose |
|----------|------|---------|
| `dateRange` | `string` | Active date range preset: `today`, `last_7_days`, `last_30_days`, `custom` |
| `customStartDate` | `?string` | Custom range start (Y-m-d) |
| `customEndDate` | `?string` | Custom range end (Y-m-d) |
| `totalSales` | `int` | Total sales amount in minor units |
| `ordersCount` | `int` | Number of orders in range |
| `averageOrderValue` | `int` | Average order value in minor units |
| `visitorsCount` | `int` | Unique visitors in range |
| `salesChange` | `float` | Percentage change vs previous period |
| `ordersChange` | `float` | Percentage change vs previous period |
| `aovChange` | `float` | Percentage change vs previous period |
| `visitorsChange` | `float` | Percentage change vs previous period |
| `ordersChartData` | `array` | Array of `{date, count}` for last 30 days |
| `topProducts` | `array` | Top 5 products: `{title, units_sold, revenue}` |
| `funnelData` | `array` | Funnel steps: `{visits, add_to_cart, checkout_started, checkout_completed}` |

| Action | Behavior |
|--------|----------|
| `updatedDateRange()` | Re-fetches all KPI data for the selected range |
| `loadKpis()` | Queries aggregated order/analytics data |
| `loadChart()` | Queries daily order counts for chart |
| `loadTopProducts()` | Queries top 5 products by revenue |
| `loadFunnel()` | Queries conversion funnel data |

| Computed Property | Purpose |
|-------------------|---------|
| `formattedTotalSales` | Formatted currency string |
| `formattedAov` | Formatted currency string |

### Wireframe

```
+------------------------------------------------------------------+
| Dashboard                           [Today v] (date range picker) |
+------------------------------------------------------------------+
|                                                                    |
|  +-------------+  +-------------+  +-------------+  +----------+  |
|  | Total Sales |  | Orders      |  | Avg Order   |  | Visitors |  |
|  | $12,345     |  | 156         |  | $79.13      |  | 2,340    |  |
|  | [+12.3%] ^  |  | [+8.1%] ^  |  | [-2.1%] v  |  | [+5%] ^  |  |
|  +-------------+  +-------------+  +-------------+  +----------+  |
|                                                                    |
|  +--------------------------------------------------------------+  |
|  | Orders over time                                              |  |
|  |  [line/bar chart - daily order counts for selected range]     |  |
|  +--------------------------------------------------------------+  |
|                                                                    |
|  +------------------------------+  +----------------------------+  |


sed -n '1,260p' specs/04-STOREFRONT-UI.md

# 04 - Storefront UI/UX Specification

> E-commerce platform - Customer-facing storefront UI/UX requirements

---

## Conventions

| Convention | Detail |
|---|---|
| CSS Framework | Tailwind CSS v4, mobile-first design, dark mode support |
| Interactivity | Livewire v4 for all dynamic UI (cart, filters, checkout) |
| Templating | Blade templates with reusable storefront-prefixed components |
| Accessibility | WCAG 2.2 AA compliance throughout |
| Responsive | Mobile-first with progressive enhancement at small, medium, large, and extra-large breakpoints |
| Currency | All prices stored as integers (cents), formatted with locale-aware helpers |
| Images | Lazy-loaded, with proper alt text and maintained aspect ratios |

---

## Currency Formatting

All monetary amounts are stored as integers in minor units (cents) and must be formatted for display according to these rules:

| Setting | Value |
|---------|-------|
| Decimal separator | `.` (period) |
| Thousands separator | `,` (comma) |
| Currency symbol | After amount with space (e.g., `24.99 EUR`) |
| Decimal places | Always 2 |
| Free amounts | Display as `0.00 EUR` |
| Negative amounts (refunds) | Prefix with `-` (e.g., `-12.50 EUR`) |

**Helper:** Use a shared Blade component or helper (e.g., `<x-price :amount="$amount" :currency="$currency" />`) to ensure consistent formatting across all storefront pages.

**Examples:**
- 2499 cents -> `24.99 EUR`
- 0 cents -> `0.00 EUR`
- 149900 cents -> `1,499.00 EUR`

---

## 1. Theme System Architecture

### 1.1 View Organization

The storefront views are organized into the following categories:

| Category | Purpose | Views |
|---|---|---|
| Layouts | Master page shell | app (master layout) |
| Pages | Core storefront pages | home, collection, product, cart, search, page (static CMS) |
| Checkout | Purchase flow | checkout index, order confirmation |
| Account | Customer area | login, register, dashboard, orders (list + detail), addresses |
| Errors | Error states | 404, 503 |
| Components | Reusable UI elements | product-card, price, badge, quantity-selector, address-form, order-summary, breadcrumbs, pagination |

### 1.2 Layout Hierarchy

All storefront pages extend a single master layout, which provides the full page shell including header, footer, cart drawer, and global Livewire listeners. Individual page templates fill the main content section.

### 1.3 Theme Settings

Theme settings are stored in the database as JSON (keyed by theme ID). A singleton service loads and caches the active theme settings for the current store. Settings control:

- Announcement bar visibility, text, link, and background color
- Sticky header toggle
- Home page section order and visibility
- Color palette overrides (primary, secondary, accent)
- Typography scale
- Footer content and social links
- Dark mode behavior (system preference, toggle, or forced)

### 1.4 Section-Based Architecture

Each page is composed of configurable sections. Sections are Blade includes or Livewire components receiving configuration from theme settings.

| Section | Description | Used On |
|---|---|---|
| Hero | Full-width banner with heading, subheading, call-to-action | Home |
| Featured Collections | Grid of collection cards | Home |
| Featured Products | Grid of product cards (lazy-loaded) | Home |
| Newsletter | Email signup form | Home, Footer |
| Rich Text | Customizable content block | Home, Pages |

### 1.5 Blade Components

All reusable UI elements are implemented as Blade components with a storefront prefix. See Section 16 for the full component library reference.

---

## 2. Base Layout

### 2.1 Document Head

The document head includes:

| Element | Description |
|---|---|
| Character encoding | UTF-8 |
| Viewport | Width = device width, initial scale 1 |
| Meta description | Page-specific, yielded from child templates |
| Title | Page-specific title appended with the app name |
| Stylesheets | Compiled CSS via Vite |
| Scripts | Compiled JS via Vite |
| Livewire styles | Injected by Livewire |
| Language attribute | Set on the HTML element from application locale |
| Scroll behavior | Smooth scrolling enabled globally |

### 2.2 Skip Link

A visually hidden "Skip to main content" link is the first element in the body. It becomes visible on keyboard focus, positioned fixed at top-left with a clear visual treatment (solid background, shadow, focus ring). Supports dark mode.

**Accessibility:** Allows keyboard users to bypass the header navigation and jump directly to the main content area.

### 2.3 Announcement Bar

Optional, controlled by theme settings. Renders a full-width bar above the header.

| Attribute | Description |
|---|---|
| Position | Full-width, above the header |
| Background | Configurable color, defaults to dark background (light text) / inverted in dark mode |
| Text | Centered, small size, configurable content |
| Link | Optional, underlined, opens in same tab |
| Dismiss | Close button (X icon) on the right side; dismis…880 chars truncated…rt Icon | Right group | Button that opens the cart drawer. Displays item count badge (small circular indicator, primary color background, white text) |
| Account Icon | Right group | Link to account dashboard (logged in) or login page (guest) |

**Mobile Layout (below large breakpoint):**

```
+--------------------------------------------------------------+
| [Hamburger]           [Logo]                            [Cart]|
+--------------------------------------------------------------+
```

| Element | Position | Description |
|---|---|---|
| Hamburger menu | Left | Toggle button for mobile navigation drawer |
| Logo | Center | Same as desktop but horizontally centered |
| Cart icon | Right | Same as desktop |

**Sticky Header (optional):**

When enabled in theme settings, the header sticks to the top of the viewport with a semi-transparent background and backdrop blur. A subtle bottom border appears on scroll.

**Mobile Navigation Drawer:**

| Attribute | Description |
|---|---|
| Animation | Slides in from the left side |
| Height | Full viewport height |
| Backdrop | Semi-transparent dark overlay behind the drawer |
| Close button | X icon at the top-right of the drawer |
| Navigation items | Vertical stack with comfortable tap targets |
| Submenus | Collapsible accordion pattern with chevron icons |
| Account link | Positioned at the bottom of the navigation list |
| Focus behavior | Focus is trapped within the drawer while open |
| ARIA | Labeled as "Mobile navigation" |

### 2.5 Main Content Area

The main content area is a landmark element with a unique ID for skip-link targeting. It occupies at least the full viewport height.

### 2.6 Footer

The footer is a content-info landmark.

**Layout:**

```
+--------------------------------------------------------------+
| [Column 1: Links]  [Column 2: Links]  [Column 3: Store Info] |
|                                                               |
| [Social Icons]                                                |
| [Copyright] [Payment Icons]                                   |
+--------------------------------------------------------------+
```

| Element | Description |
|---|---|
| Link columns | Responsive grid: 2 columns on mobile, 3 on tablet, up to 4 on desktop. Links sourced from "footer-menu" navigation menu. Each column has an uppercase heading and a list of links |
| Store info column | Store name, address (if configured), contact email |
| Social links | Row of icon links (Facebook, Instagram, Twitter/X, TikTok, YouTube) configured in theme settings. Each with screen reader label. Icons are 20x20, muted color with hover to full contrast |
| Copyright line | Small muted text: "(c) {year} {store name}. All rights reserved." |
| Payment method icons | Row of SVG icons for accepted payment methods (Visa, Mastercard, Amex, PayPal, etc.). Slightly reduced opacity |
| Spacing | Generous vertical padding, content constrained to max-width container with horizontal padding |
| Background | Light gray (dark: very dark gray), top border separator |

### 2.7 Cart Drawer (Livewire)

The cart drawer is always present in the layout as a global Livewire component. It listens for the "cart-updated" browser event to refresh and open. See Section 6 for full specification.

### 2.8 Dark Mode

Dark mode is implemented via Tailwind's dark variant. The behavior is controlled by theme settings:

| Mode | Implementation |
|---|---|
| System preference (default) | Follows OS-level dark mode preference automatically |
| Toggle | User-controlled toggle button in header/footer; preference stored in localStorage |
| Forced light/dark | Applied at server render time on the HTML element |

**Color Token Patterns:**

| Token | Light Mode | Dark Mode |
|---|---|---|
| Page background | White | Near-black |
| Card/section background | Light gray | Very dark gray |
| Heading text | Near-black | White |
| Body text | Medium gray | Light gray |
| Borders | Light gray | Medium-dark gray |
| Primary actions | Blue, darkens on hover | Slightly lighter blue, lightens on hover |

---

## 3. Home Page (GET /)

The home page renders sections in the order defined by theme settings. Each section can be toggled on/off and reordered. Default section order:

1. Hero Banner
2. Featured Collections
3. Featured Products
4. Newsletter Signup
5. Rich Text

### 3.1 Hero Banner Section

**Layout:** Full-width container (no max-width on the outer wrapper). Inner content constrained to standard max-width.

**Desktop (large breakpoint and above):**

```
+--------------------------------------------------------------+
|                                                               |
|  [Background Image / Gradient]                                |
|                                                               |
|       [Heading - very large, bold]                            |
|       [Subheading - medium, regular weight]                   |
|       [CTA Button - primary, large]                           |


Output:
Chunk ID: f72755
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 2720
Output:
# 04 - Storefront UI/UX Specification

> E-commerce platform - Customer-facing storefront UI/UX requirements

---

## Conventions

| Convention | Detail |
|---|---|
| CSS Framework | Tailwind CSS v4, mobile-first design, dark mode support |
| Interactivity | Livewire v4 for all dynamic UI (cart, filters, checkout) |
| Templating | Blade templates with reusable storefront-prefixed components |
| Accessibility | WCAG 2.2 AA compliance throughout |
| Responsive | Mobile-first with progressive enhancement at small, medium, large, and extra-large breakpoints |
| Currency | All prices stored as integers (cents), formatted with locale-aware helpers |
| Images | Lazy-loaded, with proper alt text and maintained aspect ratios |

---

## Currency Formatting

All monetary amounts are stored as integers in minor units (cents) and must be formatted for display according to these rules:

| Setting | Value |
|---------|-------|
| Decimal separator | `.` (period) |
| Thousands separator | `,` (comma) |
| Currency symbol | After amount with space (e.g., `24.99 EUR`) |
| Decimal places | Always 2 |
| Free amounts | Display as `0.00 EUR` |
| Negative amounts (refunds) | Prefix with `-` (e.g., `-12.50 EUR`) |

**Helper:** Use a shared Blade component or helper (e.g., `<x-price :amount="$amount" :currency="$currency" />`) to ensure consistent formatting across all storefront pages.

**Examples:**
- 2499 cents -> `24.99 EUR`
- 0 cents -> `0.00 EUR`
- 149900 cents -> `1,499.00 EUR`

---

## 1. Theme System Architecture

### 1.1 View Organization

The storefront views are organized into the following categories:

| Category | Purpose | Views |
|---|---|---|
| Layouts | Master page shell | app (master layout) |
| Pages | Core storefront pages | home, collection, product, cart, search, page (static CMS) |
| Checkout | Purchase flow | checkout index, order confirmation |
| Account | Customer area | login, register, dashboard, orders (list + detail), addresses |
| Errors | Error states | 404, 503 |
| Components | Reusable UI elements | product-card, price, badge, quantity-selector, address-form, order-summary, breadcrumbs, pagination |

### 1.2 Layout Hierarchy

All storefront pages extend a single master layout, which provides the full page shell including header, footer, cart drawer, and global Livewire listeners. Individual page templates fill the main content section.

### 1.3 Theme Settings

Theme settings are stored in the database as JSON (keyed by theme ID). A singleton service loads and caches the active theme settings for the current store. Settings control:

- Announcement bar visibility, text, link, and background color
- Sticky header toggle
- Home page section order and visibility
- Color palette overrides (primary, secondary, accent)
- Typography scale
- Footer content and social links
- Dark mode behavior (system preference, toggle, or forced)

### 1.4 Section-Based Architecture

Each page is composed of configurable sections. Sections are Blade includes or Livewire components receiving configuration from theme settings.

| Section | Description | Used On |
|---|---|---|
| Hero | Full-width banner with heading, subheading, call-to-action | Home |
| Featured Collections | Grid of collection cards | Home |
| Featured Products | Grid of product cards (lazy-loaded) | Home |
| Newsletter | Email signup form | Home, Footer |
| Rich Text | Customizable content block | Home, Pages |

### 1.5 Blade Components

All reusable UI elements are implemented as Blade components with a storefront prefix. See Section 16 for the full component library reference.

---

## 2. Base Layout

### 2.1 Document Head

The document head includes:

| Element | Description |
|---|---|
| Character encoding | UTF-8 |
| Viewport | Width = device width, initial scale 1 |
| Meta description | Page-specific, yielded from child templates |
| Title | Page-specific title appended with the app name |
| Stylesheets | Compiled CSS via Vite |
| Scripts | Compiled JS via Vite |
| Livewire styles | Injected by Livewire |
| Language attribute | Set on the HTML element from application locale |
| Scroll behavior | Smooth scrolling enabled globally |

### 2.2 Skip Link

A visually hidden "Skip to main content" link is the first element in the body. It becomes visible on keyboard focus, positioned fixed at top-left with a clear visual treatment (solid background, shadow, focus ring). Supports dark mode.

**Accessibility:** Allows keyboard users to bypass the header navigation and jump directly to the main content area.

### 2.3 Announcement Bar

Optional, controlled by theme settings. Renders a full-width bar above the header.

| Attribute | Description |
|---|---|
| Position | Full-width, above the header |
| Background | Configurable color, defaults to dark background (light text) / inverted in dark mode |
| Text | Centered, small size, configurable content |
| Link | Optional, underlined, opens in same tab |
| Dismiss | Close button (X icon) on the right side; dismissal persisted via localStorage |
| Height | Auto height with vertical padding |

### 2.4 Header

The header serves as the primary navigation banner.

**Desktop Layout (large breakpoint and above):**

```
+--------------------------------------------------------------+
| [Logo]      [Nav Item] [Nav Item] [Nav Item]     [S] [C] [A] |
+--------------------------------------------------------------+
```

| Element | Position | Description |
|---|---|---|
| Logo | Left | Linked to home page. Shows image if logo URL is set in theme settings, otherwise store name as text. Max height approximately 40px on desktop, 32px on smaller screens |
| Main Navigation | Center | Horizontal nav with links sourced from the "main-menu" navigation menu. Supports one level of dropdown submenus (revealed on hover/focus) |
| Search Icon | Right group | Button that opens the search modal |
| Cart Icon | Right group | Button that opens the cart drawer. Displays item count badge (small circular indicator, primary color background, white text) |
| Account Icon | Right group | Link to account dashboard (logged in) or login page (guest) |

**Mobile Layout (below large breakpoint):**

```
+--------------------------------------------------------------+
| [Hamburger]           [Logo]                            [Cart]|
+--------------------------------------------------------------+
```

| Element | Position | Description |
|---|---|---|
| Hamburger menu | Left | Toggle button for mobile navigation drawer |
| Logo | Center | Same as desktop but horizontally centered |
| Cart icon | Right | Same as desktop |

**Sticky Header (optional):**

When enabled in theme settings, the header sticks to the top of the viewport with a semi-transparent background and backdrop blur. A subtle bottom border appears on scroll.

**Mobile Navigation Drawer:**

| Attribute | Description |
|---|---|
| Animation | Slides in from the left side |
| Height | Full viewport height |
| Backdrop | Semi-transparent dark overlay behind the drawer |
| Close button | X icon at the top-right of the drawer |
| Navigation items | Vertical stack with comfortable tap targets |
| Submenus | Collapsible accordion pattern with chevron icons |
| Account link | Positioned at the bottom of the navigation list |
| Focus behavior | Focus is trapped within the drawer while open |
| ARIA | Labeled as "Mobile navigation" |

### 2.5 Main Content Area

The main content area is a landmark element with a unique ID for skip-link targeting. It occupies at least the full viewport height.

### 2.6 Footer

The footer is a content-info landmark.

**Layout:**

```
+--------------------------------------------------------------+
| [Column 1: Links]  [Column 2: Links]  [Column 3: Store Info] |
|                                                               |
| [Social Icons]                                                |
| [Copyright] [Payment Icons]                                   |
+--------------------------------------------------------------+
```

| Element | Description |
|---|---|
| Link columns | Responsive grid: 2 columns on mobile, 3 on tablet, up to 4 on desktop. Links sourced from "footer-menu" navigation menu. Each column has an uppercase heading and a list of links |
| Store info column | Store name, address (if configured), contact email |
| Social links | Row of icon links (Facebook, Instagram, Twitter/X, TikTok, YouTube) configured in theme settings. Each with screen reader label. Icons are 20x20, muted color with hover to full contrast |
| Copyright line | Small muted text: "(c) {year} {store name}. All rights reserved." |
| Payment method icons | Row of SVG icons for accepted payment methods (Visa, Mastercard, Amex, PayPal, etc.). Slightly reduced opacity |
| Spacing | Generous vertical padding, content constrained to max-width container with horizontal padding |
| Background | Light gray (dark: very dark gray), top border separator |

### 2.7 Cart Drawer (Livewire)

The cart drawer is always present in the layout as a global Livewire component. It listens for the "cart-updated" browser event to refresh and open. See Section 6 for full specification.

### 2.8 Dark Mode

Dark mode is implemented via Tailwind's dark variant. The behavior is controlled by theme settings:

| Mode | Implementation |
|---|---|
| System preference (default) | Follows OS-level dark mode preference automatically |
| Toggle | User-controlled toggle button in header/footer; preference stored in localStorage |
| Forced light/dark | Applied at server render time on the HTML element |

**Color Token Patterns:**

| Token | Light Mode | Dark Mode |
|---|---|---|
| Page background | White | Near-black |
| Card/section background | Light gray | Very dark gray |
| Heading text | Near-black | White |
| Body text | Medium gray | Light gray |
| Borders | Light gray | Medium-dark gray |
| Primary actions | Blue, darkens on hover | Slightly lighter blue, lightens on hover |

---

## 3. Home Page (GET /)

The home page renders sections in the order defined by theme settings. Each section can be toggled on/off and reordered. Default section order:

1. Hero Banner
2. Featured Collections
3. Featured Products
4. Newsletter Signup
5. Rich Text

### 3.1 Hero Banner Section

**Layout:** Full-width container (no max-width on the outer wrapper). Inner content constrained to standard max-width.

**Desktop (large breakpoint and above):**

```
+--------------------------------------------------------------+
|                                                               |
|  [Background Image / Gradient]                                |
|                                                               |
|       [Heading - very large, bold]                            |
|       [Subheading - medium, regular weight]                   |
|       [CTA Button - primary, large]                           |


rg '^### |^## Epic|^#|^#### ' specs/01-DATABASE-SCHEMA.md


rg '^### |^## |^#|^#### ' specs/07-SEEDERS-AND-TEST-DATA.md


# 08 - Playwright E2E Test Plan
## Test Suite Summary
## Test Infrastructure Requirements
### Framework
### Pest v4 Browser Testing API Reference
### Environment Configuration
### Prerequisites Before Running
## Seeded Data Dependency Table
## Test Execution Matrix
## Test Directory Structure
## Suite 1: Smoke Tests
### Test 1.1: Loads the storefront home page
### Test 1.2: Loads a collection page
### Test 1.3: Loads a product page
### Test 1.4: Loads the cart page
### Test 1.5: Loads the customer login page
### Test 1.6: Loads the admin login page
### Test 1.7: Loads the about page
### Test 1.8: Loads the search page
### Test 1.9: Loads all collections listing
### Test 1.10: Has no errors on critical pages (batch)
## Suite 2: Admin Authentication
### Authentication Flow
### Test 2.1: Can log in as admin
### Test 2.2: Shows error for invalid credentials
### Test 2.3: Shows error for empty email
### Test 2.4: Shows error for empty password
### Test 2.5: Redirects unauthenticated users to login from dashboard
### Test 2.6: Redirects unauthenticated users to login from products
### Test 2.7: Can log out
### Test 2.8: Can navigate through admin sidebar sections
### Test 2.9: Can navigate to analytics from sidebar
### Test 2.10: Can navigate to themes from sidebar
## Suite 3: Admin Product Management
### Product Lifecycle States
### Test 3.1: Shows the product list with seeded products
### Test 3.2: Can create a new product
### Test 3.3: Can edit an existing product title
### Test 3.4: Can archive a product
### Test 3.5: Shows draft products only in admin, not storefront
### Test 3.6: Can search products in admin
### Test 3.7: Can filter products by status in admin
## Suite 4: Admin Order Management
### Order State Transitions (via admin actions)
### Test 4.1: Shows the order list with seeded orders
### Test 4.2: Can filter orders by status
### Test 4.3: Shows order detail with line items and totals
### Test 4.4: Shows order timeline events
### Test 4.5: Can create a fulfillment
### Test 4.6: Can process a refund
### Test 4.7: Shows customer information in order detail
### Test 4.8: Can confirm bank transfer payment
### Test 4.9: Shows fulfillment guard for unpaid order
### Test 4.10: Can mark fulfillment as shipped
### Test 4.11: Can mark fulfillment as delivered
## Suite 5: Admin Discount Management
### Discount Types
### Test 5.1: Shows seeded discount codes
### Test 5.2: Can create a new percentage discount code
### Test 5.3: Can create a fixed amount discount code
### Test 5.4: Can create a free shipping discount code
### Test 5.5: Can edit a discount
### Test 5.6: Shows discount status indicators
## Suite 6: Admin Settings
### Settings Page Structure
### Test 6.1: Can view store settings
### Test 6.2: Can update store name
### Test 6.3: Can view shipping zones
### Test 6.4: Can add a new shipping rate to existing zone
### Test 6.5: Can view tax settings
### Test 6.6: Can update tax inclusion setting
### Test 6.7: Can view domain settings
## Suite 7: Storefront Browsing
### Storefront Page Map
### Test 7.1: Shows featured products on home page
### Test 7.2: Shows collection with product grid
### Test 7.3: Can navigate from collection to product
### Test 7.4: Shows product detail with variant options
### Test 7.5: Shows size and color option values
### Test 7.6: Updates price when variant changes on product with compare-at pricing
### Test 7.7: Shows search results for valid query
### Test 7.8: Shows no results message for invalid query
### Test 7.9: Does not show draft products on storefront collections
### Test 7.10: Does not show draft products in search results
### Test 7.11: Shows out of stock messaging for deny-policy product
### Test 7.12: Shows backorder messaging for continue-policy product
### Test 7.13: Shows new arrivals collection
### Test 7.14: Shows static about page
### Test 7.15: Navigates between pages using the main navigation
## Suite 8: Cart Flow
### Cart Interaction Flow
### Discount Code Validation Matrix
### Test 8.1: Can add product to cart
### Test 8.2: Can view cart with added item
### Test 8.3: Can update quantity in cart
### Test 8.4: Can remove item from cart
### Test 8.5: Can add multiple different products
### Test 8.6: Can apply valid discount code WELCOME10
### Test 8.7: Shows error for invalid discount code
### Test 8.8: Shows error for expired discount code
### Test 8.9: Shows error for maxed out discount code
### Test 8.10: Can apply free shipping discount
### Test 8.11: Can apply FLAT5 discount for fixed amount off
### Test 8.12: Shows subtotal and total in cart
## Suite 9: Checkout Flow
### Checkout Step Flow
### Checkout Step Detail
### Totals Calculation Reference
### Test 9.1: Completes full checkout with credit card
### Test 9.2: Shows shipping methods based on German address
### Test 9.3: Shows international shipping methods for non-DE address
### Test 9.4: Applies discount during checkout
### Test 9.5: Validates required contact email
### Test 9.6: Validates required shipping address fields
### Test 9.7: Validates invalid postal code format
### Test 9.8: Prevents checkout with empty cart
### Test 9.9: Completes checkout with PayPal
### Test 9.10: Completes checkout with bank transfer
### Test 9.11: Shows error for declined credit card (magic number)
### Test 9.12: Shows error for insufficient funds (magic number)
### Test 9.13: Switches between payment method forms
## Suite 10: Customer Account
### Customer Account Flow
### Test 10.1: Can register a new customer
### Test 10.2: Shows validation errors for duplicate email registration
### Test 10.3: Shows validation errors for mismatched passwords
### Test 10.4: Can log in as existing customer
### Test 10.5: Shows error for invalid customer credentials
### Test 10.6: Redirects unauthenticated customers to login
### Test 10.7: Shows order history for logged-in customer
### Test 10.8: Shows order detail for customer order
### Test 10.9: Can view addresses
### Test 10.10: Can add a new address
### Test 10.11: Can edit an existing address
### Test 10.12: Can log out
## Suite 11: Inventory Enforcement
### Inventory Policy Decision Matrix
### Test 11.1: Blocks add-to-cart for out-of-stock deny-policy product
### Test 11.2: Allows add-to-cart for out-of-stock continue-policy product
### Test 11.3: Shows correct stock status for in-stock product
### Test 11.4: Prevents adding more than available stock for deny-policy product
## Suite 12: Tenant Isolation
### Isolation Requirements
### Test 12.1: Store 1 only shows Store 1 products
### Test 12.2: Store 1 collections only contain Store 1 products
### Test 12.3: Admin cannot access other store data
### Test 12.4: Search only returns current store products
### Test 12.5: Customer accounts are scoped to their store
## Suite 13: Responsive / Mobile
### Viewport Definitions
### Test 13.1: Storefront home works on mobile viewport
### Test 13.2: Product page stacks layout on mobile
### Test 13.3: Can add to cart on mobile
### Test 13.4: Cart page works on mobile
### Test 13.5: Checkout flow works on mobile
### Test 13.6: Admin login works on tablet viewport
### Test 13.7: Admin sidebar navigation works on tablet
### Test 13.8: Collection page works on mobile with filters
## Suite 14: Accessibility
### Accessibility Requirements Checklist
### Test 14.1: Home page has no JavaScript errors or console warnings
### Test 14.2: Home page has proper heading hierarchy
### Test 14.3: Product page has proper ARIA labels for variant selector
### Test 14.4: Product page images have alt text
### Test 14.5: Customer login form has accessible labels
### Test 14.6: Admin login form has accessible labels
### Test 14.7: Checkout form has accessible labels
### Test 14.8: Checkout validation errors are accessible
### Test 14.9: Can navigate storefront with keyboard only
### Test 14.10: Cart page has no console errors or warnings
### Test 14.11: Search page has proper form labels
## Suite 15: Admin Collections Management
### Test 15.1: Shows the collection list with seeded collections
### Test 15.2: Can create a new collection
### Test 15.3: Can edit a collection
## Suite 16: Admin Customer Management
### Test 16.1: Shows the customer list
### Test 16.2: Shows customer detail with order history
### Test 16.3: Shows customer addresses
## Suite 17: Admin Pages Management
### Test 17.1: Shows the pages list
### Test 17.2: Can create a new page
### Test 17.3: Can edit an existing page
## Suite 18: Admin Analytics Dashboard
### Analytics Dashboard Sections
### Test 18.1: Shows the analytics dashboard
### Test 18.2: Shows sales data
### Test 18.3: Shows conversion funnel data
## Test Count Summary
## Test Execution Commands
## Implementation Notes for the Coding Agent


# 05 - Business Logic Specification
## Conventions
## 1. Multi-Tenancy and Store Isolation
### 1.1 Tenant Resolution Middleware
#### Storefront Route Resolution (from request hostname)
#### Admin Route Resolution (from session)
### 1.2 Store Scoping
### 1.3 Role-Based Access Control
## 2. Product Lifecycle
### 2.1 Status Transitions
### 2.2 Variant and Option Matrix
### 2.3 SKU Uniqueness
### 2.4 Handle/Slug Generation
## 3. Inventory Management
### 3.1 Tracking Model
### 3.2 Inventory Policy
### 3.3 Inventory Operations
### 3.4 Concurrency
## 4. Cart Service
### 4.1 Cart Lifecycle
### 4.2 Cart Operations
### 4.3 Cart Version (Optimistic Concurrency)
### 4.4 Line Amount Calculation
### 4.5 Cart Expiration
## 5. Pricing Engine
### 5.1 Calculation Order (deterministic pipeline)
### 5.2 Output Structure
### 5.3 Rounding Strategy
### 5.4 Determinism and Snapshotting
## 6. Checkout State Machine
### 6.1 States
### 6.2 Transitions and Validation
#### Transition: started -> addressed
#### Transition: addressed -> shipping_selected
#### Transition: shipping_selected -> payment_selected
#### Transition: payment_selected -> completed
#### Transition: Any active state -> expired
### 6.3 Events
## 7. Discount Engine
### 7.1 Discount Types
### Discount Status Lifecycle
### 7.2 Value Types
### 7.3 Rules
### 7.4 Validation Flow
### 7.5 Error Codes
### 7.6 Discount Allocation
### 7.7 Usage Tracking
## 8. Tax Calculation
### 8.1 Tax Provider Architecture
### 8.2 Manual Tax Mode
### 8.3 Prices Include Tax
#### Tax-inclusive pricing (prices_include_tax = true)
#### Tax-exclusive pricing (prices_include_tax = false)
### 8.4 Stripe Tax Provider (Stub)
### 8.5 Discount-Before-Tax Semantics
## 9. Shipping Calculation
### 9.1 Zone Matching
### 9.2 Rate Types
#### Flat rate
#### Weight-based rate
#### Price-based rate
#### Carrier-calculated rate (stub)
### 9.3 Requires Shipping Check
### 9.4 Free Shipping Discount Override
## 10. Payment Processing (Mock PSP)
### 10.1 Mock Payment Provider Architecture
### 10.2 Supported Payment Methods
### 10.3 Magic Card Numbers (Credit Card Testing)
### 10.4 Mock Payment Flow
### 10.5 Payment State Machine
### 10.6 Bank Transfer Instructions
### 10.7 Admin: Confirm Payment (Bank Transfer)
### 10.8 Auto-Cancel Unpaid Bank Transfer Orders
## 11. Order Management
### 11.1 Order Creation (from Completed Checkout)
### 11.2 Order Numbering
### 11.3 Order Statuses
### 11.4 Refund Processing
### 11.5 Fulfillment
#### Fulfillment Guard
#### Creating a Fulfillment
#### Fulfillment Status Transitions (Admin Button Actions)
### 11.6 Fulfillment Guard - Inventory Behavior per Payment Method
### 11.7 Auto-Fulfillment for Digital Products
### 11.6 Order Export
## 12. Customer Accounts
### 12.1 Registration
### 12.2 Authentication
### 12.3 Data Access
### 12.4 Guest Cart to Customer Linking
## 13. Webhook Delivery System
### 13.1 Subscription Model
### 13.2 Delivery Process
### 13.3 Retry Logic
### 13.4 Circuit Breaker
## 14. Analytics
### 14.1 Event Ingestion
### 14.2 Aggregation
## 15. Media Processing
### 15.1 Upload Flow
### 15.2 Processing
### 15.3 Cleanup
## 16. Search (SQLite FTS5)
### 16.1 Index Setup
### 16.2 Index Synchronization
### 16.3 Search Query
### 16.4 Search Logging
## 17. Event and Listener Registry
## 18. Scheduled Jobs Registry
## 19. Service Class Registry
## 20. Contracts (Interfaces)
## 21. Enum Registry
## 22. Value Object Registry


rg '^### |^## Epic|^#|^#### ' specs/01-DATABASE-SCHEMA.md

Question: ^### |^## Epic|^#|^####

# 01 - Database Schema Specification
## Conventions
## Epic 1 - Foundation and Multi-Store Tenancy
### organizations
### stores
### store_domains
### users
### store_users
### store_settings
## Epic 2 - Catalog, Collections, Inventory, and Media
### products
### product_options
### product_option_values
### product_variants
### variant_option_values
### inventory_items
### collections
### collection_products
### product_media
## Epic 3 - Themes, Storefront, and Localization
### themes
### theme_files
### theme_settings
### pages
### navigation_menus
### navigation_items
## Epic 4 - Search
### search_settings
### search_queries
## Epic 5 - Cart, Checkout, Discounts, Shipping, and Taxes
### carts
### cart_lines
### checkouts
### shipping_zones
### shipping_rates
### tax_settings
### discounts
## Epic 6 - Payments, Orders, Fulfillment, and Customers
### customers
### customer_addresses
### orders
### order_lines
### payments
### refunds
### fulfillments
### fulfillment_lines
## Epic 7 - Analytics
### analytics_events
### analytics_daily
## Epic 8 - Apps and Webhooks
### apps
### app_installations
### oauth_clients
### oauth_tokens
### webhook_subscriptions
### webhook_deliveries
## Laravel Framework Tables
### sessions
### cache
### cache_locks
### jobs
### job_batches
### failed_jobs
### personal_access_tokens
### password_reset_tokens
### customer_password_reset_tokens
## Entity Relationship Diagrams
### Epic 1 - Foundation and Tenancy
### Epic 2 - Catalog, Collections, Inventory, and Media
### Epic 3 - Themes, Storefront, and Localization
### Epics 5-6 - Cart, Checkout, Orders, and Payments
### Epics 7-8 - Analytics, Apps, and Webhooks
## SQLite-Specific Configuration Requirements
### Required PRAGMA Settings
### Laravel Configuration Requirements
## Migration Dependency Graph
### Batch Details
## Index Strategy Rationale
## Table Count Summary