content:
total 8
drwxr-xr-x@ 3 fabianwesner  staff    96 Feb  4 19:45 .
drwxr-xr-x@ 5 fabianwesner  staff   160 Feb  4 19:45 ..
-rw-r--r--@ 1 fabianwesner  staff  1750 Feb  4 19:45 app.css
total 0
drwxr-xr-x@ 3 fabianwesner  staff   96 Feb  4 19:45 .
drwxr-xr-x@ 5 fabianwesner  staff  160 Feb  4 19:45 ..
-rw-r--r--@ 1 fabianwesner  staff    0 Feb  4 19:45 app.js

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

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

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


userType: external

slug: iterative-seeking-pine

type: progress

type: assistant

type: message

role: assistant

Bash

command: cat /Users/fabianwesner/Herd/shop/resources/css/app.css 2>/dev/null || echo "File not found"

description: Check CSS app file

service_tier: standard

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

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

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


type: user

role: user

content:
@import 'tailwindcss';
@import '../../vendor/livewire/flux/dist/flux.css';

@source '../views';
@source '../../vendor/laravel/framework/src/Illuminate/Pagination/resources/views/*.blade.php';
@source '../../vendor/livewire/flux-pro/stubs/**/*.blade.php';
@source '../../vendor/livewire/flux/stubs/**/*.blade.php';

@custom-variant dark (&:where(.dark, .dark *));

@theme {
    --font-sans: 'Instrument Sans', ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';

    --color-zinc-50: #fafafa;
    --color-zinc-100: #f5f5f5;
    --color-zinc-200: #e5e5e5;
    --color-zinc-300: #d4d4d4;
    --color-zinc-400: #a3a3a3;
    --color-zinc-500: #737373;
    --color-zinc-600: #525252;
    --color-zinc-700: #404040;
    --color-zinc-800: #262626;
    --color-zinc-900: #171717;
    --color-zinc-950: #0a0a0a;

    --color-accent: var(--color-neutral-800);
    --color-accent-content: var(--color-neutral-800);
    --color-accent-foreground: var(--color-white);
}

@layer theme {
    .dark {
        --color-accent: var(--color-white);
        --color-accent-content: var(--color-white);
        --color-accent-foreground: var(--color-neutral-800);
    }
}

@layer base {

    *,
    ::after,
    ::before,
    ::backdrop,
    ::file-selector-button {
        border-color: var(--color-gray-200, currentColor);
    }
}

[data-flux-field]:not(ui-radio, ui-checkbox) {
    @apply grid gap-2;
}

[data-flux-label] {
    @apply  !mb-0 !leading-tight;
}

input:focus[data-flux-control],
textarea:focus[data-flux-control],
select:focus[data-flux-control] {
    @apply outline-hidden ring-2 ring-accent ring-offset-2 ring-offset-accent-foreground;
}

/* \[:where(&)\]:size-4 {
    @apply size-4;
} */

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

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

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


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

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

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


content:
     1→import {
     2→    defineConfig
     3→} from 'vite';
     4→import laravel from 'laravel-vite-plugin';
     5→import tailwindcss from "@tailwindcss/vite";
     6→
     7→export default defineConfig({
     8→    plugins: [
     9→        laravel({
    10→            input: ['resources/css/app.css', 'resources/js/app.js'],
    11→            refresh: true,
    12→        }),
    13→        tailwindcss(),
    14→    ],
    15→    server: {
    16→        cors: true,
    17→        watch: {
    18→            ignored: ['**/storage/framework/views/**'],
    19→        },
    20→    },
    21→});
    22→

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

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

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


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

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

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


content:
     1→Your mission is to implement an entire shop system based on the specifications im specs/*. You must do in one go without stopping. You must use team mode! You must test everything via Pest (unit, and functional tests). You must also additional simulate user behaviour using the Playwright MPC and confirm that all acceptance criterias are met. If you find bugs, you must fix them. The result is a perfect shop system. All requirements are perfectly implemented. All acceptance criterias are met, tested and confirmed by you.
     2→
     3→Continuously keep track of the progress in specs/progress.md Commit your progress after every relevant iteration with a meaningful message.
     4→
     5→When implementation is fully done, then make a full review meeting and showcase all features (customer- and admin-side) to me. In case bugs appear, you must fix them all and restart the review meeting.
     6→
     7→You are writing production PHP (Laravel) code.
     8→
     9→STRICT CONSTRAINTS:
    10→
    11→1. PHPStan Compliance
    12→- Must pass PHPStan at max level.
    13→- No mixed.
    14→- Explicit return types everywhere.
    15→- Fully typed properties.
    16→- No dynamic properties.
    17→- No suppressed errors.
    18→- No relying on docblocks to hide real type problems.
    19→
    20→2. Deptrac Compliance
    21→- Respect architectural layers.
    22→- No cross-layer violations.
    23→- No circular dependencies.
    24→- If a dependency is required, introduce an interface in the correct layer.
    25→- Do not modify architecture unless explicitly instructed.
    26→
    27→3. Pest Testing (Mandatory)
    28→- Every feature must include automated tests.
    29→- Include both unit and integration tests when appropriate.
    30→- Cover success path and failure paths.
    31→- Cover edge cases.
    32→- Tests must be deterministic.
    33→- Tests must validate behavior, not implementation details.
    34→
    35→4. QA Self-Verification (Mandatory)
    36→   Before finalizing:
    37→- List each acceptance criterion.
    38→- Explicitly confirm how it is implemented.
    39→- Explicitly confirm which test covers it.
    40→- Validate edge cases.
    41→- Validate negative paths.
    42→- Ensure no undefined behavior exists.
    43→
    44→5. Fresh Agent Review (Mandatory)
    45→
    46→After implementation:
    47→
    48→Step 1: A NEW agent instance must review the code.
    49→The reviewer must:
    50→- Ignore prior reasoning.
    51→- Re-evaluate architecture.
    52→- Re-evaluate PHPStan compliance.
    53→- Re-evaluate Deptrac boundaries.
    54→- Re-evaluate test coverage.
    55→- Act as a strict senior reviewer.
    56→
    57→Step 2: The reviewer must:
    58→- Identify weaknesses.
    59→- Identify overengineering.
    60→- Identify missing edge cases.
    61→- Identify architectural drift.
    62→- Suggest concrete improvements.
    63→
    64→Step 3:
    65→If issues are found:
    66→- Fix them.
    67→- Run review again with another fresh agent.
    68→- Repeat until no critical issues remain.
    69→
    70→No feature is complete without independent review.
    71→

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

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

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


type: text

text:
Excellent! Now I have a comprehensive understanding. Let me create a detailed final report:

## Comprehensive Project State Overview

I've completed a thorough exploration of the Laravel e-commerce project. Here's what currently exists vs what needs to be built:

### CURRENT PROJECT STATE

#### Dependencies (Installed)
**composer.json (PHP Packages):**
- PHP: ^8.2
- Laravel Framework: ^12.0
- Laravel Fortify: ^1.30 (authentication scaffold)
- Laravel Tinker: ^2.10.1
- Livewire: ^4.0
- Livewire Flux: ^2.9.0 (component library)
- Pint, Pest, PHPStan, Deptrac in dev-dependencies

**package.json (Node/Frontend):**
- Vite: ^7.0.4
- Tailwind CSS: ^4.0.7 with Vite plugin
- Laravel Vite Plugin: ^2.0
- Autoprefixer, Concurrently, Axios included

#### Current Database State
**Existing Migrations (4 total):**
1. `0001_01_01_000000_create_users_table.php` - Users, password_reset_tokens, sessions
2. `0001_01_01_000001_create_cache_table.php` (empty stub)
3. `0001_01_01_000002_create_jobs_table.php` (empty stub)
4. `2025_08_14_170933_add_two_factor_columns_to_users_table.php` - Two-factor auth support

**Database Configuration:**
- SQLite (database/shop.sqlite)
- File-based sessions and caching
- Foreign keys enabled

#### Current Models (1 total)
- `User.php` - Basic user model with two-factor auth support, includes initials() helper

#### Current Livewire Components (8 total)
Auth components:
- Login, Register, Forgot Password, Reset Password
- Verify Email, Confirm Password, Two-Factor Challenge

Settings components:
- Profile, Password, Appearance, TwoFactor, DeleteUserForm
- TwoFactor/RecoveryCodes (nested)

#### Current Views/Layouts
**Main Layouts:**
- `layouts/app.blade.php` - Main authenticated app layout with sidebar/header
- `layouts/auth.blade.php` - Authentication layout
- Specialized auth layouts: card, simple, split
- App sub-layouts: header, sidebar

**Auth Views (7):**
- login, register, forgot-password, reset-password, verify-email, confirm-password, two-factor-challenge

**Settings Views (6):**
- profile, password, appearance, two-factor, delete-user-form, recovery-codes

**Other Views:**
- dashboard.blade.php
- welcome.blade.php
- Components: app-logo, auth-header, action-message, placeholder-pattern, desktop-user-menu

**Flux UI Custom Components:**
- Custom icons (book-open-text, chevrons-up-down, folder-git-2, layout-grid)
- Custom navlist group component

#### Current Routes
- `routes/web.php` - Home, dashboard (protected), includes settings routes
- `routes/settings.php` - Settings paths (profile, password, appearance, two-factor)
- `routes/console.php` - Inspire command

#### Current Tests (13 total)
**Auth Tests (6 files):**
- AuthenticationTest, RegistrationTest, PasswordResetTest, PasswordConfirmationTest
- EmailVerificationTest, TwoFactorChallengeTest

**Settings Tests (3 files):**
- ProfileUpdateTest, PasswordUpdateTest, TwoFactorAuthenticationTest

**General Tests (2 files):**
- DashboardTest, ExampleTest

**Test Structure:**
- Test infrastructure in place (Pest, TestCase, factories)
- UserFactory for test data creation

#### Current Service Providers
- `AppServiceProvider` - Configures defaults (CarbonImmutable, destructive command prohibition, password rules)
- `FortifyServiceProvider` - Authentication configuration

#### Frontend Assets
- `resources/css/app.css` - Tailwind v4 (@import syntax), Flux UI styles
- `resources/js/app.js` - Empty (no JS needed yet)
- Vite configured for both CSS and JS bundling
- Tailwind theme customized with Zinc colors and Instrument Sans font

#### Configuration Files
- `.env` - All needed keys present (APP_KEY, DB settings, mail driver=log, etc.)
- `auth.php` - Web guard configured for User provider
- `database.php` - SQLite configured
- `bootstrap/app.php` - Routing configured
- `bootstrap/providers.php` - AppServiceProvider and FortifyServiceProvider registered

#### Git Status
- Current branch: `claude-code-team-2`
- Initial commit: 5cd4287
- README.md modified, composer.json modified, composer.lock modified
- /report directory present (untracked)

---

### WHAT NEEDS TO BE BUILT

Based on specs/09-IMPLEMENTATION-ROADMAP.md, here's what's missing:

#### Phase 1: Foundation (CRITICAL - Incomplete)
**Missing Migrations:**
- organizations table
- stores table
- store_domains table
- store_users pivot table
- store_settings table
- Modifications to users table (add status, last_login_at, rename password column)

**Missing Models:**
- Organization
- Store
- StoreDomain
- StoreSettings
- Customer (separate from User)

**Missing Enums:**
- StoreStatus (Active, Suspended)
- StoreUserRole (Owner, Admin, Staff, Support)
- UserStatus (active, disabled)
- OrderStatus, PaymentStatus, FulfillmentStatus, etc.

**Missing Config Updates:**
- auth.php - Add customer guard and customers provider
- Add customer password broker

**Missing Core Classes:**
- StoreUser pivot model
- Customer model/guard separation

#### Phase 2: Catalog (Not Started)
**Missing Models:**
- Product
- ProductVariant
- ProductImage
- Category
- Collection
- Brand/Vendor

**Missing Migrations:**
- products, product_variants, product_images tables
- categories, product_categories junction
- collections, product_collections junction
- Inventory tracking tables

#### Phase 3: Themes and Storefront Layout (Not Started)
**Missing:**
- Storefront views (not admin layout)
- Theme system
- Product listing pages
- Product detail pages
- Category browsing

#### Phase 4: Cart, Checkout, Discounts, Shipping, Taxes (Not Started)
**Missing Models:**
- Cart/CartItem
- Discount/DiscountCode
- ShippingMethod
- TaxRate

**Missing Migrations:**
- carts, cart_items
- discounts, discount_codes
- shipping_methods
- tax_rates

#### Phase 5: Payments, Orders, Fulfillment (Not Started)
**Missing Models:**
- Order, OrderItem
- Payment, PaymentMethod
- Fulfillment, FulfillmentItem
- Refund

**Missing Features:**
- Mock PSP (payment provider)
- Order processing
- Payment handling

#### Phase 6: Customer Accounts (Partial - only auth exists)
**Missing:**
- Customer dashboard
- Order history view
- Address book
- Wishlist
- Profile management (customer-specific)

#### Phase 7: Admin Panel (Not Started)
**Missing:**
- Admin dashboard
- Product management UI (Livewire components)
- Order management UI
- Customer management UI
- Settings/configuration UI
- Analytics views

#### Phase 8: Search (Not Started)
**Missing:**
- SQLite FTS5 implementation
- Search API endpoints
- Search UI components

#### Phase 9: Analytics (Not Started)
**Missing:**
- Analytics models
- Reporting UI
- Charts/graphs

#### Phase 10: Apps and Webhooks (Deferred)
**Missing:**
- OAuth/app integration
- Webhook system

#### Phase 11: Polish (Not Started)
**Missing:**
- Performance optimizations
- Error handling improvements
- UI refinements

#### Phase 12: Full Test Suite (Not Started)
**Missing:**
- E2E browser tests with Playwright
- Additional feature tests
- Edge case coverage

---

### SUMMARY TABLE

| Component | Status | Count |
|-----------|--------|-------|
| **Models** | Partial | 1/20+ |
| **Migrations** | Partial | 4/50+ |
| **Livewire Components** | Partial | 8/100+ |
| **Views** | Partial | 20/100+ |
| **Tests** | Partial | 13/200+ |
| **API Routes** | Not Started | 0/100+ |
| **Enums** | Not Started | 0/15+ |
| **Controllers** | Minimal | 1/30+ |
| **Database Schema** | Partial | 1/12 core tables |

---

### KEY MISSING ARTIFACTS

1. **Multi-tenant architecture** - organizations/stores not implemented
2. **Customer/admin separation** - only generic users exist
3. **Product catalog system** - zero product-related code
4. **E-commerce core** - no cart, checkout, orders, payments
5. **Admin UI** - views exist but no admin-specific components
6. **API routes** - only web routes exist, no API endpoints
7. **Search system** - no search implementation
8. **Comprehensive tests** - only auth tests present
9. **Webhook system** - not started
10. **Customizable themes** - not started

---

### NEXT STEPS (Per README)

Your mission is to implement the complete shop system in one continuous session using team mode, with:
- Strict PHPStan compliance (max level, no mixed types)
- Deptrac architecture compliance
- Comprehensive Pest tests (unit + functional)
- Playwright E2E testing with user behavior simulation
- Progress tracking in specs/progress.md
- Git commits after each iteration
- Full review meeting when complete

The implementation roadmap in specs/09-IMPLEMENTATION-ROADMAP.md provides the detailed build order starting with Phase 1 (Foundation).

text:
agentId: afa76cc (for resuming to continue this agent's work if needed)
<usage>total_tokens: 56195
tool_uses: 32
duration_ms: 58930</usage>

status: completed

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

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

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

speed: standard


type: thinking

thinking:
The second agent couldn't read all specs at once. Let me read the remaining spec files myself in parallel, focusing on key details I need for implementation.

Actually, I already have the complete implementation roadmap (spec 09) which is the most detailed file. Let me read the other specs in chunks to get the key details I need.

But actually, this is a MASSIVE project - ~320-380 files to create. Let me think about the best approach.

Given the constraints:
1. Must use team mode
2. Must implement everything in one go
3. Must test everything with Pest
4. Must test with Playwright
5. Must track progress in specs/progress.md

6. Must commit after every relevant iteration
7. Must do a full review meeting at the end

The implementation roadmap already provides extremely detailed specs for what to build. I need to organize this work by creating a team structure - a lead coordinator, backend agents for models and services, frontend agents for views and components, and test specialists - then tackle Phase 1 first since everything else depends on it before parallelizing the remaining phases.

Given the scale of this project with 320-380 files across multiple phases, I should spawn specialized agents to handle different phases in parallel while I orchestrate the overall coordination and ensure each phase is completed fully before moving to the next dependency.


text: Now I have a comprehensive understanding of the project. This is a massive undertaking (~320-380 files). Let me read the key remaining specs and then create a team to implement this.


Task

description: Read database schema spec

prompt: Read the file /Users/fabianwesner/Herd/shop/specs/01-DATABASE-SCHEMA.md in chunks (500 lines at a time) and provide a comprehensive summary of ALL table definitions including: table names, column names with types, foreign keys, indexes, constraints, and enums. I need every detail for implementation.


text: Read the file /Users/fabianwesner/Herd/shop/specs/01-DATABASE-SCHEMA.md in chunks (500 lines at a time) and provide a comprehensive summary of ALL table definitions including: table names, column names with types, foreign keys, indexes, constraints, and enums. I need every detail for implementation.

prompt: Read the file /Users/fabianwesner/Herd/shop/specs/01-DATABASE-SCHEMA.md in chunks (500 lines at a time) and provide a comprehensive summary of ALL table definitions including: table names, column names with types, foreign keys, indexes, constraints, and enums. I need every detail for implementation.


description: Read API routes spec

prompt: Read the file /Users/fabianwesner/Herd/shop/specs/02-API-ROUTES.md in chunks (500 lines at a time) and provide a comprehensive summary of ALL API routes including: method, URL, middleware, controller/handler, request/response formats, and validation rules. I need every detail for implementation.


text: Read the file /Users/fabianwesner/Herd/shop/specs/02-API-ROUTES.md in chunks (500 lines at a time) and provide a comprehensive summary of ALL API routes including: method, URL, middleware, controller/handler, request/response formats, and validation rules. I need every detail for implementation.

prompt: Read the file /Users/fabianwesner/Herd/shop/specs/02-API-ROUTES.md in chunks (500 lines at a time) and provide a comprehensive summary of ALL API routes including: method, URL, middleware, controller/handler, request/response formats, and validation rules. I need every detail for implementation.


prompt: Read the file /Users/fabianwesner/Herd/shop/specs/01-DATABASE-SCHEMA.md in chunks (500 lines at a time) and provide a comprehensive summary of ALL table definitions including: table names, column names with types, foreign keys, indexes, constraints, and enums. I need every detail for implementation.


content:
     1→# 01 - Database Schema Specification
     2→
     3→> E-commerce platform - SQLite database schema for Laravel 12
     4→
     5→---
     6→
     7→## Conventions
     8→
     9→| Convention | Detail |
    10→|---|---|
    11→| Primary keys | Auto-incrementing integer primary key on every table (column: `id`) |
    12→| Tenant isolation | `store_id` foreign key on every tenant-scoped table |
    13→| Monetary amounts | Stored as **integer in minor units** (cents). Column names end in `_amount`. |
    14→| Booleans | Integer, not null, default 0 (0 = false, 1 = true) |
    15→| JSON data | Text column - Laravel casts to/from JSON at the application layer |
    16→| Enumerations | Check constraints on text columns |
    17→| Timestamps | Text (ISO-8601) via Laravel's `created_at` / `updated_at` |
    18→| Soft deletes | Only where explicitly noted (`deleted_at` text, nullable) |
    19→| Encrypted columns | Suffixed `_encrypted` - encrypted at application layer, stored as text |
    20→
    21→---
    22→
    23→## Epic 1 - Foundation and Multi-Store Tenancy
    24→
    25→### organizations
    26→
    27→Billing entity that owns one or more stores.
    28→
    29→| Column | Type | Nullable | Default | Constraints | Notes |
    30→|---|---|---|---|---|---|
    31→| id | INTEGER | No | autoincrement | PRIMARY KEY | |
    32→| name | TEXT | No | - | - | Organization display name |
    33→| billing_email | TEXT | No | - | - | Primary billing contact |
    34→| created_at | TEXT | Yes | NULL | - | ISO-8601 timestamp |
    35→| updated_at | TEXT | Yes | NULL | - | ISO-8601 timestamp |
    36→
    37→**Indexes:**
    38→
    39→- `idx_organizations_billing_email` on (`billing_email`) - Supports lookup by billing email
    40→
    41→---
    42→
    43→### stores
    44→
    45→Individual storefront belonging to an organization.
    46→
    47→| Column | Type | Nullable | Default | Constraints | Notes |
    48→|---|---|---|---|---|---|
    49→| id | INTEGER | No | autoincrement | PRIMARY KEY | |
    50→| organization_id | INTEGER | No | - | FK -> organizations(id) ON DELETE CASCADE | Parent organization |
    51→| name | TEXT | No | - | - | Store display name |
    52→| handle | TEXT | No | - | UNIQUE per table | URL-friendly slug |
    53→| status | TEXT | No | 'active' | CHECK IN ('active', 'suspended') | |
    54→| default_currency | TEXT | No | 'USD' | - | ISO 4217 currency code |
    55→| default_locale | TEXT | No | 'en' | - | IETF locale tag |
    56→| timezone | TEXT | No | 'UTC' | - | IANA timezone |
    57→| created_at | TEXT | Yes | NULL | - | ISO-8601 timestamp |
    58→| updated_at | TEXT | Yes | NULL | - | ISO-8601 timestamp |
    59→
    60→**Indexes:**
    61→
    62→- `idx_stores_handle` UNIQUE on (`handle`) - Ensures globally unique store handles for URL routing
    63→- `idx_stores_organization_id` on (`organization_id`) - FK lookup and listing stores by org
    64→- `idx_stores_status` on (`status`) - Filtering stores by active/suspended status
    65→
    66→---
    67→
    68→### store_domains
    69→
    70→Custom domains attached to a store.
    71→
    72→| Column | Type | Nullable | Default | Constraints | Notes |
    73→|---|---|---|---|---|---|
    74→| id | INTEGER | No | autoincrement | PRIMARY KEY | |
    75→| store_id | INTEGER | No | - | FK -> stores(id) ON DELETE CASCADE | Owning store |
    76→| hostname | TEXT | No | - | UNIQUE per table | e.g. "shop.example.com" |
    77→| type | TEXT | No | 'storefront' | CHECK IN ('storefront', 'admin', 'api') | Domain purpose |
    78→| is_primary | INTEGER | No | 0 | - | Boolean: primary domain flag |
    79→| tls_mode | TEXT | No | 'managed' | CHECK IN ('managed', 'bring_your_own') | TLS certificate mode |
    80→| created_at | TEXT | Yes | NULL | - | ISO-8601 timestamp |
    81→
    82→**Indexes:**
    83→
    84→- `idx_store_domains_hostname` UNIQUE on (`hostname`) - Globally unique hostname for routing
    85→- `idx_store_domains_store_id` on (`store_id`) - FK lookup, listing domains per store
    86→- `idx_store_domains_store_primary` on (`store_id`, `is_primary`) - Quick lookup of a store's primary domain
    87→
    88→---
    89→
    90→### users
    91→
    92→Admin/staff users who manage stores (not storefront customers).
    93→
    94→| Column | Type | Nullable | Default | Constraints | Notes |
    95→|---|---|---|---|---|---|
    96→| id | INTEGER | No | autoincrement | PRIMARY KEY | |
    97→| email | TEXT | No | - | UNIQUE per table | Login email |
    98→| password_hash | TEXT | No | - | - | bcrypt/argon2 hash; maps to Laravel's `password` field |
    99→| name | TEXT | No | - | - | Display name |
   100→| status | TEXT | No | 'active' | CHECK IN ('active', 'disabled') | |
   101→| email_verified_at | TEXT | Yes | NULL | - | ISO-8601 timestamp; required for `verified` middleware |
   102→| last_login_at | TEXT | Yes | NULL | - | ISO-8601 timestamp |
   103→| two_factor_secret | TEXT | Yes | NULL | - | TOTP secret |
   104→| two_factor_recovery_codes | TEXT | Yes | NULL | - | JSON array of recovery codes |
   105→| two_factor_confirmed_at | TEXT | Yes | NULL | - | ISO-8601 timestamp |
   106→| remember_token | TEXT | Yes | NULL | - | Laravel remember-me token |
   107→| created_at | TEXT | Yes | NULL | - | ISO-8601 timestamp |
   108→| updated_at | TEXT | Yes | NULL | - | ISO-8601 timestamp |
   109→
   110→**Indexes:**
   111→
   112→- `idx_users_email` UNIQUE on (`email`) - Enforces unique email, supports login lookup
   113→- `idx_users_status` on (`status`) - Filtering users by active/disabled
   114→
   115→**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.
   116→
   117→---
   118→
   119→### store_users
   120→
   121→Pivot table linking users to stores with a role. Uses composite primary key.
   122→
   123→| Column | Type | Nullable | Default | Constraints | Notes |
   124→|---|---|---|---|---|---|
   125→| store_id | INTEGER | No | - | PK (composite), FK -> stores(id) ON DELETE CASCADE | |
   126→| user_id | INTEGER | No | - | PK (composite), FK -> users(id) ON DELETE CASCADE | |
   127→| role | TEXT | No | 'staff' | CHECK IN ('owner', 'admin', 'staff', 'support') | |
   128→| created_at | TEXT | Yes | NULL | - | ISO-8601 timestamp |
   129→
   130→**Primary Key:** (`store_id`, `user_id`)
   131→
   132→**Indexes:**
   133→
   134→- `idx_store_users_user_id` on (`user_id`) - Reverse lookup: find all stores for a user
   135→- `idx_store_users_role` on (`store_id`, `role`) - Listing users by role within a store
   136→
   137→---
   138→
   139→### store_settings
   140→
   141→Single-row-per-store JSON settings bag.
   142→
   143→| Column | Type | Nullable | Default | Constraints | Notes |
   144→|---|---|---|---|---|---|
   145→| store_id | INTEGER | No | - | PRIMARY KEY, FK -> stores(id) ON DELETE CASCADE | One-to-one with stores |
   146→| settings_json | TEXT | No | '{}' | - | JSON settings object |
   147→| updated_at | TEXT | Yes | NULL | - | ISO-8601 timestamp |
   148→
   149→**Notes:** No additional indexes needed - primary key on `store_id` covers all queries.
   150→
   151→---
   152→
   153→## Epic 2 - Catalog, Collections, Inventory, and Media
   154→
   155→### products
   156→
   157→Core product record, scoped to a store.
   158→
   159→| Column | Type | Nullable | Default | Constraints | Notes |
   160→|---|---|---|---|---|---|
   161→| id | INTEGER | No | autoincrement | PRIMARY KEY | |
   162→| store_id | INTEGER | No | - | FK -> stores(id) ON DELETE CASCADE | Owning store |
   163→| title | TEXT | No | - | - | Product display title |
   164→| handle | TEXT | No | - | UNIQUE per (store_id, handle) | URL-friendly slug |
   165→| status | TEXT | No | 'draft' | CHECK IN ('draft', 'active', 'archived') | |
   166→| description_html | TEXT | Yes | NULL | - | Rich-text description |
   167→| vendor | TEXT | Yes | NULL | - | Manufacturer/brand |
   168→| product_type | TEXT | Yes | NULL | - | Category classification |
   169→| tags | TEXT | No | '[]' | - | JSON array, e.g. ["summer","sale"] |
   170→| published_at | TEXT | Yes | NULL | - | ISO-8601 timestamp; null = unpublished |
   171→| created_at | TEXT | Yes | NULL | - | ISO-8601 timestamp |
   172→| updated_at | TEXT | Yes | NULL | - | ISO-8601 timestamp |
   173→
   174→**Indexes:**
   175→
   176→- `idx_products_store_handle` UNIQUE on (`store_id`, `handle`) - Unique handle per store for URL routing
   177→- `idx_products_store_id` on (`store_id`) - FK lookup, listing all products in a store
   178→- `idx_products_store_status` on (`store_id`, `status`) - Filtering products by status within a store
   179→- `idx_products_published_at` on (`store_id`, `published_at`) - Sorting/filtering by publication date
   180→- `idx_products_vendor` on (`store_id`, `vendor`) - Filtering products by vendor
   181→- `idx_products_product_type` on (`store_id`, `product_type`) - Filtering products by type
   182→
   183→**Notes:** `tags` is a JSON-encoded array (e.g. `["summer","sale"]`). The `handle` is unique per store, not globally.
   184→
   185→---
   186→
   187→### product_options
   188→
   189→Named option dimensions for a product (e.g. Size, Color).
   190→
   191→| Column | Type | Nullable | Default | Constraints | Notes |
   192→|---|---|---|---|---|---|
   193→| id | INTEGER | No | autoincrement | PRIMARY KEY | |
   194→| product_id | INTEGER | No | - | FK -> products(id) ON DELETE CASCADE | Parent product |
   195→| name | TEXT | No | - | - | Option name, e.g. "Size", "Color" |
   196→| position | INTEGER | No | 0 | UNIQUE per (product_id, position) | Display ordering |
   197→
   198→**Indexes:**
   199→
   200→- `idx_product_options_product_id` on (`product_id`) - FK lookup, listing options per product
   201→- `idx_product_options_product_position` UNIQUE on (`product_id`, `position`) - Enforces unique ordering per product
   202→
   203→---
   204→
   205→### product_option_values
   206→
   207→Individual values within a product option (e.g. "Small", "Medium", "Large").
   208→
   209→| Column | Type | Nullable | Default | Constraints | Notes |
   210→|---|---|---|---|---|---|
   211→| id | INTEGER | No | autoincrement | PRIMARY KEY | |
   212→| product_option_id | INTEGER | No | - | FK -> product_options(id) ON DELETE CASCADE | Parent option |
   213→| value | TEXT | No | - | - | Display value, e.g. "Small" |
   214→| position | INTEGER | No | 0 | UNIQUE per (product_option_id, position) | Display ordering |
   215→
   216→**Indexes:**
   217→
   218→- `idx_product_option_values_option_id` on (`product_option_id`) - FK lookup, listing values per option
   219→- `idx_product_option_values_option_position` UNIQUE on (`product_option_id`, `position`) - Enforces unique ordering per option
   220→
   221→---
   222→
   223→### product_variants
   224→
   225→Purchasable SKU-level variant of a product.
   226→
   227→| Column | Type | Nullable | Default | Constraints | Notes |
   228→|---|---|---|---|---|---|
   229→| id | INTEGER | No | autoincrement | PRIMARY KEY | |
   230→| product_id | INTEGER | No | - | FK -> products(id) ON DELETE CASCADE | Parent product |
   231→| sku | TEXT | Yes | NULL | - | Stock keeping unit |
   232→| barcode | TEXT | Yes | NULL | - | UPC/EAN barcode |
   233→| price_amount | INTEGER | No | 0 | - | Price in minor units (cents) |
   234→| compare_at_amount | INTEGER | Yes | NULL | - | Original/compare price in minor units |
   235→| currency | TEXT | No | 'USD' | - | ISO 4217 currency code |
   236→| weight_g | INTEGER | Yes | NULL | - | Weight in grams |
   237→| requires_shipping | INTEGER | No | 1 | - | Boolean: physical product flag |
   238→| is_default | INTEGER | No | 0 | - | Boolean: default variant flag |
   239→| position | INTEGER | No | 0 | - | Display ordering |
   240→| status | TEXT | No | 'active' | CHECK IN ('active', 'archived') | |
   241→| created_at | TEXT | Yes | NULL | - | ISO-8601 timestamp |
   242→| updated_at | TEXT | Yes | NULL | - | ISO-8601 timestamp |
   243→
   244→**Indexes:**
   245→
   246→- `idx_product_variants_product_id` on (`product_id`) - FK lookup, listing variants per product
   247→- `idx_product_variants_sku` on (`sku`) - SKU lookup across all variants
   248→- `idx_product_variants_barcode` on (`barcode`) - Barcode scanning lookup
   249→- `idx_product_variants_product_position` on (`product_id`, `position`) - Ordered variant listing
   250→- `idx_product_variants_product_default` on (`product_id`, `is_default`) - Quick lookup of default variant
   251→
   252→**Notes:** `price_amount` and `compare_at_amount` are in minor currency units (cents). A product always has at least one variant (the default).
   253→
   254→---
   255→
   256→### variant_option_values
   257→
   258→Maps a variant to its chosen option values (e.g. variant #5 = Size:Large + Color:Red). Junction table with composite primary key.
   259→
   260→| Column | Type | Nullable | Default | Constraints | Notes |
   261→|---|---|---|---|---|---|
   262→| variant_id | INTEGER | No | - | PK (composite), FK -> product_variants(id) ON DELETE CASCADE | |
   263→| product_option_value_id | INTEGER | No | - | PK (composite), FK -> product_option_values(id) ON DELETE CASCADE | |
   264→
   265→**Primary Key:** (`variant_id`, `product_option_value_id`)
   266→
   267→**Indexes:**
   268→
   269→- `idx_variant_option_values_value_id` on (`product_option_value_id`) - Reverse lookup: find all variants with a given option value
   270→
   271→**Notes:** This is a junction table that links variants to the specific option values they represent. Required to resolve which combination of options each variant corresponds to.
   272→
   273→---
   274→
   275→### inventory_items
   276→
   277→Stock tracking per variant per store.
   278→
   279→| Column | Type | Nullable | Default | Constraints | Notes |
   280→|---|---|---|---|---|---|
   281→| id | INTEGER | No | autoincrement | PRIMARY KEY | |
   282→| store_id | INTEGER | No | - | FK -> stores(id) ON DELETE CASCADE | Owning store |
   283→| variant_id | INTEGER | No | - | FK -> product_variants(id) ON DELETE CASCADE, UNIQUE | One inventory record per variant |
   284→| quantity_on_hand | INTEGER | No | 0 | - | Total physical stock |
   285→| quantity_reserved | INTEGER | No | 0 | - | Stock allocated to open orders |
   286→| policy | TEXT | No | 'deny' | CHECK IN ('deny', 'continue') | Oversell policy |
   287→
   288→**Indexes:**
   289→
   290→- `idx_inventory_items_variant_id` UNIQUE on (`variant_id`) - Enforces one inventory record per variant, fast variant lookup
   291→- `idx_inventory_items_store_id` on (`store_id`) - Listing all inventory for a store
   292→
   293→**Notes:** `quantity_on_hand` is the total physical stock. `quantity_reserved` is stock allocated to open orders not yet fulfilled. Available = on_hand - reserved. Policy `deny` prevents overselling; `continue` allows selling below zero.
   294→
   295→---
   296→
   297→### collections
   298→
   299→Curated or rule-based product groupings.
   300→
   301→| Column | Type | Nullable | Default | Constraints | Notes |
   302→|---|---|---|---|---|---|
   303→| id | INTEGER | No | autoincrement | PRIMARY KEY | |
   304→| store_id | INTEGER | No | - | FK -> stores(id) ON DELETE CASCADE | Owning store |
   305→| title | TEXT | No | - | - | Collection display title |
   306→| handle | TEXT | No | - | UNIQUE per (store_id, handle) | URL-friendly slug |
   307→| description_html | TEXT | Yes | NULL | - | Rich-text description |
   308→| type | TEXT | No | 'manual' | CHECK IN ('manual', 'automated') | Collection type: manual curation or rule-based automation |
   309→| status | TEXT | No | 'active' | CHECK IN ('draft', 'active', 'archived') | |
   310→| created_at | TEXT | Yes | NULL | - | ISO-8601 timestamp |
   311→| updated_at | TEXT | Yes | NULL | - | ISO-8601 timestamp |
   312→
   313→**Indexes:**
   314→
   315→- `idx_collections_store_handle` UNIQUE on (`store_id`, `handle`) - Unique handle per store for URL routing
   316→- `idx_collections_store_id` on (`store_id`) - FK lookup, listing all collections in a store
   317→- `idx_collections_store_status` on (`store_id`, `status`) - Filtering collections by status
   318→
   319→---
   320→
   321→### collection_products
   322→
   323→Pivot linking products to collections with ordering. Composite primary key.
   324→
   325→| Column | Type | Nullable | Default | Constraints | Notes |
   326→|---|---|---|---|---|---|
   327→| collection_id | INTEGER | No | - | PK (composite), FK -> collections(id) ON DELETE CASCADE | |
   328→| product_id | INTEGER | No | - | PK (composite), FK -> products(id) ON DELETE CASCADE | |
   329→| position | INTEGER | No | 0 | - | Display ordering within collection |
   330→
   331→**Primary Key:** (`collection_id`, `product_id`)
   332→
   333→**Indexes:**
   334→
   335→- `idx_collection_products_product_id` on (`product_id`) - Reverse lookup: find all collections containing a product
   336→- `idx_collection_products_position` on (`collection_id`, `position`) - Ordered product listing within a collection
   337→
   338→---
   339→
   340→### product_media
   341→
   342→Images and videos attached to a product.
   343→
   344→| Column | Type | Nullable | Default | Constraints | Notes |
   345→|---|---|---|---|---|---|
   346→| id | INTEGER | No | autoincrement | PRIMARY KEY | |
   347→| product_id | INTEGER | No | - | FK -> products(id) ON DELETE CASCADE | Parent product |
   348→| type | TEXT | No | 'image' | CHECK IN ('image', 'video') | Media type |
   349→| storage_key | TEXT | No | - | - | File storage path/key |
   350→| alt_text | TEXT | Yes | NULL | - | Accessibility alt text |
   351→| width | INTEGER | Yes | NULL | - | Pixel width |
   352→| height | INTEGER | Yes | NULL | - | Pixel height |
   353→| mime_type | TEXT | Yes | NULL | - | e.g. "image/jpeg" |
   354→| byte_size | INTEGER | Yes | NULL | - | File size in bytes |
   355→| position | INTEGER | No | 0 | - | Display ordering |
   356→| status | TEXT | No | 'processing' | CHECK IN ('processing', 'ready', 'failed') | Processing status |
   357→| created_at | TEXT | Yes | NULL | - | ISO-8601 timestamp |
   358→
   359→**Indexes:**
   360→
   361→- `idx_product_media_product_id` on (`product_id`) - FK lookup, listing media per product
   362→- `idx_product_media_product_position` on (`product_id`, `position`) - Ordered media listing
   363→- `idx_product_media_status` on (`status`) - Finding media by processing status
   364→
   365→---
   366→
   367→## Epic 3 - Themes, Storefront, and Localization
   368→
   369→### themes
   370→
   371→Storefront theme definitions per store.
   372→
   373→| Column | Type | Nullable | Default | Constraints | Notes |
   374→|---|---|---|---|---|---|
   375→| id | INTEGER | No | autoincrement | PRIMARY KEY | |
   376→| store_id | INTEGER | No | - | FK -> stores(id) ON DELETE CASCADE | Owning store |
   377→| name | TEXT | No | - | - | Theme display name |
   378→| version | TEXT | Yes | NULL | - | Semantic version string |
   379→| status | TEXT | No | 'draft' | CHECK IN ('draft', 'published') | |
   380→| published_at | TEXT | Yes | NULL | - | ISO-8601 timestamp |
   381→| created_at | TEXT | Yes | NULL | - | ISO-8601 timestamp |
   382→| updated_at | TEXT | Yes | NULL | - | ISO-8601 timestamp |
   383→
   384→**Indexes:**
   385→
   386→- `idx_themes_store_id` on (`store_id`) - FK lookup, listing themes per store
   387→- `idx_themes_store_status` on (`store_id`, `status`) - Finding published/draft themes per store
   388→
   389→---
   390→
   391→### theme_files
   392→
   393→Individual files belonging to a theme (templates, assets, snippets).
   394→
   395→| Column | Type | Nullable | Default | Constraints | Notes |
   396→|---|---|---|---|---|---|
   397→| id | INTEGER | No | autoincrement | PRIMARY KEY | |
   398→| theme_id | INTEGER | No | - | FK -> themes(id) ON DELETE CASCADE | Parent theme |
   399→| path | TEXT | No | - | UNIQUE per (theme_id, path) | File path within theme |
   400→| storage_key | TEXT | No | - | - | File storage path/key |
   401→| sha256 | TEXT | No | - | - | Content hash for cache busting |
   402→| byte_size | INTEGER | No | 0 | - | File size in bytes |
   403→
   404→**Indexes:**
   405→
   406→- `idx_theme_files_theme_path` UNIQUE on (`theme_id`, `path`) - Enforces unique file paths per theme
   407→- `idx_theme_files_theme_id` on (`theme_id`) - FK lookup, listing files per theme
   408→
   409→---
   410→
   411→### theme_settings
   412→
   413→Single-row JSON configuration per theme.
   414→
   415→| Column | Type | Nullable | Default | Constraints | Notes |
   416→|---|---|---|---|---|---|
   417→| theme_id | INTEGER | No | - | PRIMARY KEY, FK -> themes(id) ON DELETE CASCADE | One-to-one with themes |
   418→| settings_json | TEXT | No | '{}' | - | JSON settings object |
   419→| updated_at | TEXT | Yes | NULL | - | ISO-8601 timestamp |
   420→
   421→---
   422→
   423→### pages
   424→
   425→Static CMS pages (About Us, Contact, etc.).
   426→
   427→| Column | Type | Nullable | Default | Constraints | Notes |
   428→|---|---|---|---|---|---|
   429→| id | INTEGER | No | autoincrement | PRIMARY KEY | |
   430→| store_id | INTEGER | No | - | FK -> stores(id) ON DELETE CASCADE | Owning store |
   431→| title | TEXT | No | - | - | Page title |
   432→| handle | TEXT | No | - | UNIQUE per (store_id, handle) | URL-friendly slug |
   433→| body_html | TEXT | Yes | NULL | - | Rich-text content |
   434→| status | TEXT | No | 'draft' | CHECK IN ('draft', 'published', 'archived') | |
   435→| published_at | TEXT | Yes | NULL | - | ISO-8601 timestamp |
   436→| created_at | TEXT | Yes | NULL | - | ISO-8601 timestamp |
   437→| updated_at | TEXT | Yes | NULL | - | ISO-8601 timestamp |
   438→
   439→**Indexes:**
   440→
   441→- `idx_pages_store_handle` UNIQUE on (`store_id`, `handle`) - Unique handle per store for URL routing
   442→- `idx_pages_store_id` on (`store_id`) - FK lookup, listing pages per store
   443→- `idx_pages_store_status` on (`store_id`, `status`) - Filtering pages by status
   444→
   445→---
   446→
   447→### navigation_menus
   448→
   449→Named menus (e.g. main-menu, footer-menu).
   450→
   451→| Column | Type | Nullable | Default | Constraints | Notes |
   452→|---|---|---|---|---|---|
   453→| id | INTEGER | No | autoincrement | PRIMARY KEY | |
   454→| store_id | INTEGER | No | - | FK -> stores(id) ON DELETE CASCADE | Owning store |
   455→| handle | TEXT | No | - | UNIQUE per (store_id, handle) | e.g. "main-menu" |
   456→| title | TEXT | No | - | - | Display title |
   457→| created_at | TEXT | Yes | NULL | - | ISO-8601 timestamp |
   458→| updated_at | TEXT | Yes | NULL | - | ISO-8601 timestamp |
   459→
   460→**Indexes:**
   461→
   462→- `idx_navigation_menus_store_handle` UNIQUE on (`store_id`, `handle`) - Unique handle per store
   463→- `idx_navigation_menus_store_id` on (`store_id`) - FK lookup, listing menus per store
   464→
   465→---
   466→
   467→### navigation_items
   468→
   469→Individual links within a navigation menu (supports nesting via position).
   470→
   471→| Column | Type | Nullable | Default | Constraints | Notes |
   472→|---|---|---|---|---|---|
   473→| id | INTEGER | No | autoincrement | PRIMARY KEY | |
   474→| menu_id | INTEGER | No | - | FK -> navigation_menus(id) ON DELETE CASCADE | Parent menu |
   475→| type | TEXT | No | 'link' | CHECK IN ('link', 'page', 'collection', 'product') | Item type |
   476→| label | TEXT | No | - | - | Display label |
   477→| url | TEXT | Yes | NULL | - | Direct URL (used when type = 'link') |
   478→| resource_id | INTEGER | Yes | NULL | - | FK to pages/collections/products (polymorphic) |
   479→| position | INTEGER | No | 0 | - | Display ordering |
   480→
   481→**Indexes:**
   482→
   483→- `idx_navigation_items_menu_id` on (`menu_id`) - FK lookup, listing items per menu
   484→- `idx_navigation_items_menu_position` on (`menu_id`, `position`) - Ordered item listing
   485→
   486→**Notes:** When `type` is `link`, the `url` field is used directly. When `type` is `page`, `collection`, or `product`, the `resource_id` points to the respective table's `id`. The URL is resolved at render time.
   487→
   488→---
   489→
   490→## Epic 4 - Search
   491→
   492→### search_settings
   493→
   494→Per-store search configuration (synonyms, stop words).
   495→
   496→| Column | Type | Nullable | Default | Constraints | Notes |
   497→|---|---|---|---|---|---|
   498→| store_id | INTEGER | No | - | PRIMARY KEY, FK -> stores(id) ON DELETE CASCADE | One-to-one with stores |
   499→| synonyms_json | TEXT | No | '[]' | - | JSON array of synonym groups |
   500→| stop_words_json | TEXT | No | '[]' | - | JSON array of stop words |

prompt: Read the file /Users/fabianwesner/Herd/shop/specs/01-DATABASE-SCHEMA.md in chunks (500 lines at a time) and provide a comprehensive summary of ALL table definitions including: table names, column names with types, foreign keys, indexes, constraints, and enums. I need every detail for implementation.


description: Read business logic spec

prompt: Read the file /Users/fabianwesner/Herd/shop/specs/05-BUSINESS-LOGIC.md in chunks (500 lines at a time) and provide a comprehensive summary of ALL business logic rules including: services, state machines, validation rules, pricing calculations, inventory management, payment processing, and fulfillment rules. I need every detail for implementation.


text: Read the file /Users/fabianwesner/Herd/shop/specs/05-BUSINESS-LOGIC.md in chunks (500 lines at a time) and provide a comprehensive summary of ALL business logic rules including: services, state machines, validation rules, pricing calculations, inventory management, payment processing, and fulfillment rules. I need every detail for implementation.

prompt: Read the file /Users/fabianwesner/Herd/shop/specs/05-BUSINESS-LOGIC.md in chunks (500 lines at a time) and provide a comprehensive summary of ALL business logic rules including: services, state machines, validation rules, pricing calculations, inventory management, payment processing, and fulfillment rules. I need every detail for implementation.


prompt: Read the file /Users/fabianwesner/Herd/shop/specs/02-API-ROUTES.md in chunks (500 lines at a time) and provide a comprehensive summary of ALL API routes including: method, URL, middleware, controller/handler, request/response formats, and validation rules. I need every detail for implementation.


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

prompt: Read the file /Users/fabianwesner/Herd/shop/specs/02-API-ROUTES.md in chunks (500 lines at a time) and provide a comprehensive summary of ALL API routes including: method, URL, middleware, controller/handler, request/response formats, and validation rules. I need every detail for implementation.


prompt: Read the file /Users/fabianwesner/Herd/shop/specs/05-BUSINESS-LOGIC.md in chunks (500 lines at a time) and provide a comprehensive summary of ALL business logic rules including: services, state machines, validation rules, pricing calculations, inventory management, payment processing, and fulfillment rules. I need every detail for implementation.


content:
     1→# 05 - Business Logic Specification
     2→
     3→> E-commerce platform - All domain logic, state machines, calculations, and rules for Laravel 12
     4→
     5→---
     6→
     7→## Conventions
     8→
     9→| Convention | Detail |
    10→|---|---|
    11→| Monetary amounts | **INTEGER in minor units** (cents). All arithmetic uses integers - never floating point. |
    12→| Percentage storage | Basis points where noted (e.g., 1900 = 19.00%) or whole-number percentage in `discounts.value_amount` (e.g., 15 = 15%) |
    13→| Rounding | Round each line independently, then sum (prevents rounding drift) |
    14→| Database | SQLite - single-writer, WAL mode, no advisory locks needed |
    15→| Time | All timestamps in ISO-8601 UTC via Laravel `created_at` / `updated_at` |
    16→| Currency | Single currency per store (`stores.default_currency`). No multi-currency conversion. |
    17→
    18→---
    19→
    20→## 1. Multi-Tenancy and Store Isolation
    21→
    22→### 1.1 Tenant Resolution Middleware
    23→
    24→**Class:** `App\Http\Middleware\ResolveStore`
    25→
    26→**Register** in `bootstrap/app.php` under both the `storefront` and `admin` middleware groups.
    27→
    28→#### Storefront Route Resolution (from request hostname)
    29→
    30→1. Extract hostname from the incoming HTTP request.
    31→2. Look up `store_domains` table where `hostname` matches.
    32→3. Cache the hostname-to-store_id mapping using file cache with a 5-minute TTL. Cache key pattern: `store_domain:{hostname}`.
    33→4. Retrieve the full Store model and bind it as a singleton in the application container under the key `current_store`.
    34→5. If hostname is not found in `store_domains`: return HTTP 404.
    35→6. If the resolved store has `status = 'suspended'`: return HTTP 503 maintenance page.
    36→
    37→#### Admin Route Resolution (from session)
    38→
    39→1. Read the selected store ID from the authenticated user's session (key: `current_store_id`).
    40→2. Verify the authenticated user has a `store_users` record for that store.
    41→3. If the store is suspended: return HTTP 403 Forbidden. Admin users can still view settings but cannot perform storefront-affecting mutations.
    42→
    43→```mermaid
    44→flowchart TD
    45→    A[Incoming Request] --> B{Route type?}
    46→    B -->|Storefront| C[Extract hostname]
    47→    C --> D{Hostname in cache?}
    48→    D -->|Yes| E[Load store from cache]
    49→    D -->|No| F[Query store_domains table]
    50→    F --> G{Found?}
    51→    G -->|No| H[Return 404]
    52→    G -->|Yes| I[Cache hostname->store_id, 5 min TTL]
    53→    I --> E
    54→    E --> J{Store suspended?}
    55→    J -->|Yes| K[Return 503 maintenance]
    56→    J -->|No| L[Bind store as singleton, continue]
    57→
    58→    B -->|Admin| M[Read store_id from session]
    59→    M --> N{User has store_users record?}
    60→    N -->|No| O[Return 403]
    61→    N -->|Yes| P{Store suspended?}
    62→    P -->|Yes| Q[Return 403 for mutations]
    63→    P -->|No| L
    64→```
    65→
    66→### 1.2 Store Scoping
    67→
    68→**Trait:** `App\Models\Concerns\BelongsToStore`
    69→**Global Scope:** `App\Models\Scopes\StoreScope`
    70→
    71→The trait applies a global query scope to every model that uses it. The scope adds a `WHERE store_id = <current_store_id>` clause to all queries automatically.
    72→
    73→**Behavior:**
    74→
    75→- On the model's `creating` event, automatically set `store_id` from the current store context (defense-in-depth).
    76→- All SELECT, UPDATE, DELETE queries on tenant-scoped models include the store filter.
    77→- In platform-admin contexts where cross-store access is needed, the scope can be temporarily removed.
    78→
    79→**Models that must use this trait:**
    80→products, collections, customers, orders, carts, checkouts, discounts, shipping_zones, themes, pages, navigation_menus, analytics_events, analytics_daily, webhook_subscriptions, inventory_items, search_queries.
    81→
    82→### 1.3 Role-Based Access Control
    83→
    84→**Enum:** `App\Enums\StoreUserRole` - backed by string values: `owner`, `admin`, `staff`, `support`
    85→
    86→**Permission Matrix:**
    87→
    88→| Permission | Owner | Admin | Staff | Support |
    89→|---|---|---|---|---|
    90→| Manage store settings | Yes | Yes | No | No |
    91→| Manage staff | Yes | Yes | No | No |
    92→| Manage products | Yes | Yes | Yes | No |
    93→| Manage orders | Yes | Yes | Yes | Yes (read-only) |
    94→| Manage discounts | Yes | Yes | Yes | No |
    95→| Manage themes | Yes | Yes | No | No |
    96→| Manage apps | Yes | Yes | No | No |
    97→| View analytics | Yes | Yes | Yes | No |
    98→| Manage shipping/tax | Yes | Yes | No | No |
    99→| Process refunds | Yes | Yes | No | No |
   100→| Create fulfillments | Yes | Yes | Yes | No |
   101→
   102→**Implementation requirements:**
   103→
   104→- Create a Laravel policy for each resource model (e.g., `ProductPolicy`, `OrderPolicy`).
   105→- Register gate definitions in a service provider.
   106→- Each policy method receives the User, looks up the user's role for the current store via the `store_users` pivot table, and checks the matrix above.
   107→- Add a helper method on the User model - `roleForStore(store)` - that returns the user's role for a given store or null if the user has no access.
   108→
   109→---
   110→
   111→## 2. Product Lifecycle
   112→
   113→### 2.1 Status Transitions
   114→
   115→**Enum:** `App\Enums\ProductStatus` - values: `draft`, `active`, `archived`
   116→
   117→```mermaid
   118→stateDiagram-v2
   119→    [*] --> Draft : Product created
   120→    Draft --> Active : Publish (requires variant with price > 0, title not empty)
   121→    Draft --> Archived : Archive
   122→    Active --> Archived : Archive (hide from storefront)
   123→    Archived --> Active : Re-publish (same preconditions as Draft->Active)
   124→    Active --> Draft : Revert (BLOCKED if order_lines exist)
   125→    Archived --> Draft : Revert (BLOCKED if order_lines exist)
   126→```
   127→
   128→**Transition rules:**
   129→
   130→| From | To | Preconditions |
   131→|---|---|---|
   132→| Draft | Active | At least one variant with `price_amount > 0`; product `title` is not empty |
   133→| Draft | Archived | None |
   134→| Active | Archived | None (soft transition: product hidden from storefront, preserved in order history) |
   135→| Archived | Active | Same preconditions as Draft to Active |
   136→| Active | Draft | **BLOCKED** if any `order_lines` reference variants of this product |
   137→| Archived | Draft | **BLOCKED** if any `order_lines` reference variants of this product |
   138→
   139→**Deletion rules:**
   140→- Only `draft` products with zero `order_lines` references may be hard-deleted.
   141→- Products with existing order references must be archived, never deleted, to preserve order history integrity.
   142→
   143→**Service:** `App\Services\ProductService`
   144→- Method `transitionStatus(product, newStatus)` validates the current state, checks preconditions, throws `InvalidProductTransitionException` on failure.
   145→- On activation: set `published_at` to current time if not already set.
   146→- Dispatch `ProductStatusChanged` event after successful transition.
   147→
   148→### 2.2 Variant and Option Matrix
   149→
   150→**Rules:**
   151→
   152→- A product has 0 to 3 options (e.g., Size, Color, Material), stored in `product_options`.
   153→- Each option has 1 or more values, stored in `product_option_values`.
   154→- Variants represent the cartesian product of option values (or a manually curated subset).
   155→- When a product has no options, a single "Default" variant is auto-created with `is_default = 1`.
   156→
   157→**Matrix rebuild logic (when options change):**
   158→
   159→```
   160→PROCEDURE rebuildMatrix(product):
   161→    current_option_values = load all option values grouped by option
   162→    desired_combos = cartesianProduct(current_option_values)
   163→    existing_variants = load all variants with their option value sets
   164→
   165→    FOR EACH combo IN desired_combos:
   166→        IF combo matches an existing variant's option value set:
   167→            SKIP (preserve existing variant - prices, SKUs, inventory intact)
   168→        ELSE:
   169→            CREATE new variant with default pricing from first existing variant
   170→
   171→    FOR EACH existing_variant NOT matching any desired combo:
   172→        IF variant has order_line references:
   173→            ARCHIVE variant (set status = 'archived')
   174→        ELSE:
   175→            DELETE variant
   176→```
   177→
   178→**Service:** `App\Services\VariantMatrixService`
   179→
   180→### 2.3 SKU Uniqueness
   181→
   182→- Combination of store_id (derived through the product's store) and SKU must be unique.
   183→- Enforced at application level before save.
   184→- Null or empty SKU values are exempt from the uniqueness check (multiple variants may have no SKU).
   185→- Validation: query all variants belonging to products in the same store, check for SKU collision excluding the current variant's ID.
   186→
   187→### 2.4 Handle/Slug Generation
   188→
   189→**Helper class:** `App\Support\HandleGenerator`
   190→
   191→- Auto-generate from the title by converting to a URL-safe slug (lowercase, hyphens for spaces, strip special characters).
   192→- Uniqueness enforced per store via the database unique index on `(store_id, handle)`.
   193→- On collision, append incrementing suffix: `my-product`, `my-product-1`, `my-product-2`, etc.
   194→- Merchants can manually edit the handle; on save, re-validate uniqueness.
   195→- Same logic applies to collections and pages.
   196→
   197→---
   198→
   199→## 3. Inventory Management
   200→
   201→### 3.1 Tracking Model
   202→
   203→Each `product_variant` has exactly one `inventory_item` record (created automatically when the variant is created).
   204→
   205→| Field | Description |
   206→|---|---|
   207→| `quantity_on_hand` | Total physical stock count |
   208→| `quantity_reserved` | Units held for active checkouts or pending orders |
   209→| **Available** (computed) | `quantity_on_hand - quantity_reserved` |
   210→
   211→### 3.2 Inventory Policy
   212→
   213→Per-variant policy stored in `inventory_items.policy`:
   214→
   215→| Policy | Behavior |
   216→|---|---|
   217→| `deny` | Block add-to-cart when available <= 0. Storefront displays "Out of stock". |
   218→| `continue` | Allow add-to-cart regardless of stock level. When available <= 0, storefront displays "Available on backorder". |
   219→
   220→### 3.3 Inventory Operations
   221→
   222→**Service:** `App\Services\InventoryService`
   223→
   224→All operations must execute within a database transaction.
   225→
   226→```mermaid
   227→flowchart LR
   228→    subgraph "Stock Lifecycle"
   229→        A[On Hand] -->|Reserve| B[Reserved]
   230→        B -->|Commit on payment| C[Deducted]
   231→        B -->|Release on expiry| A
   232→        C -->|Restock on refund| A
   233→    end
   234→```
   235→
   236→**Operation definitions:**
   237→
   238→| Operation | Trigger | Effect on `quantity_on_hand` | Effect on `quantity_reserved` | Pre-check |
   239→|---|---|---|---|---|
   240→| **Reserve** | Checkout enters `payment_selected` | No change | +quantity | If policy=deny, verify available >= quantity. Throw `InsufficientInventoryException` if not. |
   241→| **Release** | Checkout expires or is abandoned | No change | -quantity | None |
   242→| **Commit** | Payment confirmed, order created | -quantity | -quantity | None (reservation already validated) |
   243→| **Restock** | Refund processed with restock flag | +quantity | No change | None |
   244→
   245→### 3.4 Concurrency
   246→
   247→SQLite operates in single-writer mode with WAL journaling. Database transactions provide sufficient isolation. No optimistic locking or advisory locks are needed.
   248→
   249→---
   250→
   251→## 4. Cart Service
   252→
   253→### 4.1 Cart Lifecycle
   254→
   255→**Service:** `App\Services\CartService`
   256→
   257→```mermaid
   258→stateDiagram-v2
   259→    [*] --> active : Cart created
   260→    active --> converted : Checkout completed successfully
   261→    active --> abandoned : Cleanup job (14 days inactive)
   262→```
   263→
   264→**Cart identification:**
   265→- Guest users: cart ID stored in session cookie.
   266→- Authenticated customers: cart linked via `customer_id` foreign key.
   267→
   268→The guest cart is bound to the session using the key `cart_id`. When a guest visits the storefront, a cart is created on first add-to-cart and the cart's ID is stored in `session('cart_id')`. On customer login, if a session cart exists, it is merged with any existing customer cart (the session cart's items are added to the customer cart, preferring the higher quantity for duplicate variants). After merge, the session key is cleared.
   269→
   270→**Cart merge on login:**
   271→
   272→```
   273→PROCEDURE mergeGuestCart(guestCart, customerCart):
   274→    FOR EACH line IN guestCart.lines:
   275→        existingLine = customerCart.findLineByVariant(line.variant_id)
   276→        IF existingLine EXISTS:
   277→            existingLine.quantity = MAX(existingLine.quantity, line.quantity)
   278→            RECALCULATE existingLine amounts
   279→        ELSE:
   280→            MOVE line to customerCart
   281→    MARK guestCart as abandoned
   282→    RECALCULATE all line amounts on customerCart
   283→```
   284→
   285→### 4.2 Cart Operations
   286→
   287→**Create cart:**
   288→- Accepts: store, optional customer
   289→- Sets: store_id, currency from store's default_currency, initial cart_version = 1
   290→
   291→**Add line - validations (in order):**
   292→
   293→1. Variant exists and belongs to the current store (through its product).
   294→2. Product has status = `active`.
   295→3. Variant has status = `active`.
   296→4. Inventory policy check: if policy = `deny`, verify available >= requested quantity.
   297→5. If the variant already exists in the cart, increment existing line quantity instead of creating a duplicate.
   298→6. Set `unit_price_amount` from the variant's `price_amount`.
   299→7. Calculate: `line_subtotal_amount = unit_price_amount * quantity`.
   300→8. Set `line_discount_amount = 0` (recalculated by discount engine during checkout).
   301→9. Calculate: `line_total_amount = line_subtotal_amount - line_discount_amount`.
   302→10. Increment `cart_version`.
   303→
   304→**Update quantity:**
   305→- Validate new quantity > 0. If 0, remove the line instead.
   306→- Re-check inventory policy for the new quantity.
   307→- Recalculate line amounts.
   308→- Increment `cart_version`.
   309→
   310→**Remove line:**
   311→- Delete the `cart_lines` record.
   312→- Increment `cart_version`.
   313→
   314→### 4.3 Cart Version (Optimistic Concurrency)
   315→
   316→Every cart mutation increments `cart_version` by 1.
   317→
   318→**For API clients:**
   319→- Client sends `expected_version` with each mutation request.
   320→- If cart's current version does not match the expected version, return HTTP 409 Conflict with the current cart state in the response body.
   321→- This prevents race conditions when multiple browser tabs or devices modify the same cart.
   322→
   323→**For Livewire/session-based flows:**
   324→- Still implemented for consistency, but less critical since requests are serialized per session.
   325→
   326→### 4.4 Line Amount Calculation
   327→
   328→```
   329→line_subtotal_amount = unit_price_amount * quantity
   330→line_discount_amount = 0  (updated by discount engine during checkout)
   331→line_total_amount    = line_subtotal_amount - line_discount_amount
   332→```
   333→
   334→All values are integers in minor currency units.
   335→
   336→### 4.5 Cart Expiration
   337→
   338→**Job:** `App\Jobs\CleanupAbandonedCarts`
   339→
   340→- Schedule: daily
   341→- Finds carts where status = `active` AND updated_at < (now - 14 days). The 14-day threshold is configurable via `store_settings`.
   342→- Marks them as `abandoned`.
   343→- Releases any reserved inventory associated with active checkouts for these carts.
   344→
   345→---
   346→
   347→## 5. Pricing Engine
   348→
   349→### 5.1 Calculation Order (deterministic pipeline)
   350→
   351→**Service:** `App\Services\PricingEngine`
   352→
   353→The pricing engine follows a strict, deterministic sequence. The same inputs must always produce the same output.
   354→
   355→```mermaid
   356→flowchart TD
   357→    A["Step 1: Line Subtotals
   358→    FOR EACH line: subtotal = unit_price * quantity"] --> B["Step 2: Cart Subtotal
   359→    SUM of all line subtotals"]
   360→    B --> C["Step 3: Discount
   361→    Apply discount rules (Section 7)
   362→    Allocate proportionally across qualifying lines"]
   363→    C --> D["Step 4: Discounted Subtotal
   364→    subtotal - total_discount"]
   365→    D --> E["Step 5: Shipping
   366→    Based on selected method and rate rules (Section 9)"]
   367→    E --> F["Step 6: Tax
   368→    Calculated on discounted line amounts
   369→    + shipping if taxable (Section 8)"]
   370→    F --> G["Step 7: Total
   371→    discounted_subtotal + shipping + tax_total"]
   372→```
   373→
   374→### 5.2 Output Structure
   375→
   376→**Value Object:** `App\ValueObjects\PricingResult`
   377→
   378→| Field | Type | Description |
   379→|---|---|---|
   380→| `subtotal` | integer (cents) | Sum of all line subtotals before discount |
   381→| `discount` | integer (cents) | Total discount amount |
   382→| `shipping` | integer (cents) | Shipping cost |
   383→| `taxLines` | array of TaxLine | Individual tax line items |
   384→| `taxTotal` | integer (cents) | Sum of all tax line amounts |
   385→| `total` | integer (cents) | Final amount: discounted_subtotal + shipping + taxTotal |
   386→| `currency` | string | ISO 4217 currency code |
   387→
   388→**Value Object:** `App\ValueObjects\TaxLine`
   389→
   390→| Field | Type | Description |
   391→|---|---|---|
   392→| `name` | string | Tax label (e.g., "State Tax") |
   393→| `rate` | integer | Tax rate in basis points (e.g., 800 = 8.00%) |
   394→| `amount` | integer (cents) | Calculated tax amount |
   395→
   396→### 5.3 Rounding Strategy
   397→
   398→- Each line's discount allocation, tax, and total are rounded independently.
   399→- Totals are computed by summing already-rounded line amounts.
   400→- This prevents accumulation of fractional cents across lines.
   401→- Any remainder from proportional discount allocation is added to the last qualifying line (largest-remainder method).
   402→
   403→### 5.4 Determinism and Snapshotting
   404→
   405→- After each calculation, store the full result in `checkouts.totals_json` as a JSON snapshot.
   406→- Recalculate on every significant state change: address change (may affect tax/shipping zones), shipping method change, discount code change.
   407→- The snapshot structure mirrors the PricingResult value object.
   408→
   409→---
   410→
   411→## 6. Checkout State Machine
   412→
   413→### 6.1 States
   414→
   415→**Enum:** `App\Enums\CheckoutStatus` - values: `started`, `addressed`, `shipping_selected`, `payment_selected`, `completed`, `expired`
   416→
   417→```mermaid
   418→stateDiagram-v2
   419→    [*] --> started : Checkout created from cart
   420→    started --> addressed : Address provided
   421→    addressed --> shipping_selected : Shipping method chosen
   422→    shipping_selected --> payment_selected : Payment method selected
   423→    payment_selected --> completed : Payment confirmed
   424→    payment_selected --> expired : Timeout (24h)
   425→
   426→    started --> expired : Timeout (24h)
   427→    addressed --> expired : Timeout (24h)
   428→    shipping_selected --> expired : Timeout (24h)
   429→
   430→    note right of addressed : Triggers pricing recalculation
   431→    note right of shipping_selected : Triggers pricing recalculation
   432→    note right of payment_selected : Inventory reserved
   433→    note right of completed : Order created, inventory committed
   434→    note right of expired : Reserved inventory released
   435→```
   436→
   437→### 6.2 Transitions and Validation
   438→
   439→**Service:** `App\Services\CheckoutService`
   440→
   441→#### Transition: started -> addressed
   442→
   443→**Method:** `setAddress(checkout, addressData)`
   444→
   445→**Required data:**
   446→- `email` - valid email address
   447→- `shipping_address` with required fields:
   448→
   449→| Field | Required | Type | Notes |
   450→|---|---|---|---|
   451→| `first_name` | Yes | string | |
   452→| `last_name` | Yes | string | |
   453→| `address1` | Yes | string | |
   454→| `address2` | No | string | |
   455→| `company` | No | string | |
   456→| `city` | Yes | string | |
   457→| `province` | No | string | |
   458→| `province_code` | No | string | Used for zone matching |
   459→| `country` | Yes | string | 2-letter ISO 3166-1 code |
   460→| `postal_code` | Yes | string | |
   461→| `phone` | No | string | |
   462→
   463→**Actions:**
   464→1. Validate address fields using a dedicated Form Request (`SetCheckoutAddressRequest`).
   465→2. Save email and shipping_address_json on the checkout.
   466→3. Copy shipping address to billing_address_json by default (can be overridden later).
   467→4. Recalculate pricing (shipping zones and tax regions may change based on address).
   468→5. Transition status to `addressed`.
   469→6. Dispatch `CheckoutAddressed` event.
   470→
   471→#### Transition: addressed -> shipping_selected
   472→
   473→**Method:** `setShippingMethod(checkout, shippingRateId)`
   474→
   475→**Requires:**
   476→- A valid shipping_rate_id that belongs to a shipping zone matching the checkout's shipping address.
   477→- **Skip condition:** If no cart lines have `requires_shipping = true` on their variant, skip this step. Set status to `shipping_selected` with shipping_method_id = null and shipping_amount = 0.
   478→
   479→**Actions:**
   480→1. Resolve available shipping zones for the address (see Section 9).
   481→2. Verify the selected rate belongs to an applicable zone.
   482→3. Save shipping_method_id on the checkout.
   483→4. Recalculate pricing with the selected shipping rate.
   484→5. Transition status to `shipping_selected`.
   485→6. Dispatch `CheckoutShippingSelected` event.
   486→
   487→#### Transition: shipping_selected -> payment_selected
   488→
   489→**Method:** `selectPaymentMethod(checkout, paymentMethod)` - validates and stores the selected payment method
   490→
   491→**Requires:**
   492→- All previous data is valid (address, shipping method).
   493→- Totals have been calculated and stored.
   494→- `paymentMethod` must be one of: `credit_card`, `paypal`, `bank_transfer`.
   495→
   496→**Actions:**
   497→1. Store `payment_method` on the checkout record.
   498→2. Reserve inventory for all cart lines (call InventoryService.reserve for each line).
   499→3. Set `expires_at` on the checkout to current time + 24 hours.
   500→4. Transition status to `payment_selected`.

prompt: Read the file /Users/fabianwesner/Herd/shop/specs/05-BUSINESS-LOGIC.md in chunks (500 lines at a time) and provide a comprehensive summary of ALL business logic rules including: services, state machines, validation rules, pricing calculations, inventory management, payment processing, and fulfillment rules. I need every detail for implementation.


description: Read auth/security spec

prompt: Read the file /Users/fabianwesner/Herd/shop/specs/06-AUTH-AND-SECURITY.md in chunks (500 lines at a time) and provide a comprehensive summary of ALL auth and security specifications including: guards, providers, middleware, rate limiting, permission matrices, CSRF, and session management. I need every detail for implementation.


text: Read the file /Users/fabianwesner/Herd/shop/specs/06-AUTH-AND-SECURITY.md in chunks (500 lines at a time) and provide a comprehensive summary of ALL auth and security specifications including: guards, providers, middleware, rate limiting, permission matrices, CSRF, and session management. I need every detail for implementation.

prompt: Read the file /Users/fabianwesner/Herd/shop/specs/06-AUTH-AND-SECURITY.md in chunks (500 lines at a time) and provide a comprehensive summary of ALL auth and security specifications including: guards, providers, middleware, rate limiting, permission matrices, CSRF, and session management. I need every detail for implementation.


prompt: Read the file /Users/fabianwesner/Herd/shop/specs/02-API-ROUTES.md in chunks (500 lines at a time) and provide a comprehensive summary of ALL API routes including: method, URL, middleware, controller/handler, request/response formats, and validation rules. I need every detail for implementation.


content:
     1→# API and Routing Specification
     2→
     3→This document defines every route, endpoint, middleware stack, request/response schema, and rate limit for the e-commerce platform. The admin panel is built with Livewire v4 + Flux UI (server-rendered), while the storefront uses Blade + Livewire. REST API endpoints serve cart/checkout operations and programmatic admin access.
     4→
     5→All monetary amounts are stored and transmitted in **minor units** (cents). All IDs are auto-incrementing integers. All monetary amounts are in minor units (cents).
     6→
     7→---
     8→
     9→## 1. Web Routes (Blade/Livewire Pages)
    10→
    11→### 1.1 Admin Auth Routes
    12→
    13→No middleware beyond the default `web` group. These routes handle admin login and logout.
    14→
    15→| Method | Path | Description | Component / Action |
    16→|--------|------|-------------|--------------------|
    17→| GET | `/admin/login` | Admin login page | `Livewire: Admin\Auth\Login` |
    18→| POST | `/admin/login` | Process admin login | Handled by Livewire action |
    19→| POST | `/admin/logout` | Logout and invalidate session | Handled by Livewire action |
    20→| GET | `/admin/forgot-password` | Show forgot password form | `Livewire: Admin\Auth\ForgotPassword` |
    21→| POST | `/admin/forgot-password` | Send password reset link | Handled by Livewire action |
    22→| GET | `/admin/reset-password/{token}` | Show reset password form | `Livewire: Admin\Auth\ResetPassword` |
    23→| POST | `/admin/reset-password` | Reset password with token | Handled by Livewire action |
    24→
    25→Rate limit: `login` tier (5 per minute per IP).
    26→
    27→---
    28→
    29→### 1.2 Admin Routes
    30→
    31→**Prefix:** `/admin`
    32→**Middleware:** `web`, `auth`, `verified`, `store.resolve`, `role.check`
    33→
    34→All admin pages render Livewire full-page components. Data mutations happen via Livewire actions (not separate POST endpoints).
    35→
    36→> **Note:** The Create and Edit routes render the same Livewire form component (`ProductForm`, `OrderForm`, etc.) in create vs edit mode, following the shared-form pattern defined in Spec 03.
    37→
    38→#### Dashboard
    39→
    40→| Method | Path | Description | Component |
    41→|--------|------|-------------|-----------|
    42→| GET | `/admin` | Dashboard with KPI tiles, orders chart, conversion funnel | `Admin\Dashboard` |
    43→
    44→#### Products
    45→
    46→| Method | Path | Description | Component |
    47→|--------|------|-------------|-----------|
    48→| GET | `/admin/products` | Product list - searchable, filterable by status, bulk actions | `Admin\Products\Index` |
    49→| GET | `/admin/products/create` | Create product form | `Admin\Products\Create` |
    50→| GET | `/admin/products/{product}/edit` | Edit product form with variants, media, options | `Admin\Products\Edit` |
    51→
    52→#### Inventory
    53→
    54→| Method | Path | Description | Component |
    55→|--------|------|-------------|-----------|
    56→| GET | `/admin/inventory` | Inventory management page | `Admin\Inventory\Index` |
    57→
    58→#### Collections
    59→
    60→| Method | Path | Description | Component |
    61→|--------|------|-------------|-----------|
    62→| GET | `/admin/collections` | Collection list | `Admin\Collections\Index` |
    63→| GET | `/admin/collections/create` | Create collection form | `Admin\Collections\Create` |
    64→| GET | `/admin/collections/{collection}/edit` | Edit collection form | `Admin\Collections\Edit` |
    65→
    66→#### Orders
    67→
    68→| Method | Path | Description | Component |
    69→|--------|------|-------------|-----------|
    70→| GET | `/admin/orders` | Order list with status filters | `Admin\Orders\Index` |
    71→| GET | `/admin/orders/{order}` | Order detail with timeline, payments, fulfillments | `Admin\Orders\Show` |
    72→
    73→#### Customers
    74→
    75→| Method | Path | Description | Component |
    76→|--------|------|-------------|-----------|
    77→| GET | `/admin/customers` | Customer list | `Admin\Customers\Index` |
    78→| GET | `/admin/customers/{customer}` | Customer detail with order history, addresses | `Admin\Customers\Show` |
    79→
    80→#### Discounts
    81→
    82→| Method | Path | Description | Component |
    83→|--------|------|-------------|-----------|
    84→| GET | `/admin/discounts` | Discount list | `Admin\Discounts\Index` |
    85→| GET | `/admin/discounts/create` | Create discount form with rules builder | `Admin\Discounts\Create` |
    86→| GET | `/admin/discounts/{discount}/edit` | Edit discount form | `Admin\Discounts\Edit` |
    87→
    88→#### Settings
    89→
    90→| Method | Path | Description | Component |
    91→|--------|------|-------------|-----------|
    92→| GET | `/admin/settings` | Store settings (tabs: General, Domains, Shipping, Taxes, Checkout, Notifications) | `Admin\Settings\Index` |
    93→| GET | `/admin/settings/shipping` | Shipping zones and rates configuration | `Admin\Settings\Shipping` |
    94→| GET | `/admin/settings/taxes` | Tax settings - mode, provider config, rates | `Admin\Settings\Taxes` |
    95→
    96→#### Themes and Online Store
    97→
    98→| Method | Path | Description | Component |
    99→|--------|------|-------------|-----------|
   100→| GET | `/admin/themes` | Theme management - cards with actions (Customize, Preview, Publish, Duplicate, Delete) | `Admin\Themes\Index` |
   101→| GET | `/admin/themes/{theme}/editor` | Theme editor - left panel sections, center preview iframe, right panel settings | `Admin\Themes\Editor` |
   102→
   103→#### Pages
   104→
   105→| Method | Path | Description | Component |
   106→|--------|------|-------------|-----------|
   107→| GET | `/admin/pages` | Content pages list | `Admin\Pages\Index` |
   108→| GET | `/admin/pages/create` | Create content page | `Admin\Pages\Create` |
   109→| GET | `/admin/pages/{page}/edit` | Edit content page | `Admin\Pages\Edit` |
   110→
   111→#### Navigation
   112→
   113→| Method | Path | Description | Component |
   114→|--------|------|-------------|-----------|
   115→| GET | `/admin/navigation` | Navigation menus management | `Admin\Navigation\Index` |
   116→
   117→#### Apps
   118→
   119→| Method | Path | Description | Component |
   120→|--------|------|-------------|-----------|
   121→| GET | `/admin/apps` | Apps directory - list installed and available apps | `Admin\Apps\Index` |
   122→| GET | `/admin/apps/{installation}` | Installed app detail - scopes, webhooks, usage | `Admin\Apps\Show` |
   123→
   124→#### Developers
   125→
   126→| Method | Path | Description | Component |
   127→|--------|------|-------------|-----------|
   128→| GET | `/admin/developers` | Developer settings - API keys/tokens, webhook management | `Admin\Developers\Index` |
   129→
   130→> **Note:** Webhook subscription management (CRUD) is available exclusively through the admin UI at `/admin/developers`. There are no REST API endpoints for programmatic webhook subscription management in the initial implementation.
   131→
   132→#### Analytics
   133→
   134→| Method | Path | Description | Component |
   135→|--------|------|-------------|-----------|
   136→| GET | `/admin/analytics` | Analytics dashboard - sales, traffic, funnel, exports | `Admin\Analytics\Index` |
   137→
   138→#### Search Settings
   139→
   140→| Method | Path | Description | Component |
   141→|--------|------|-------------|-----------|
   142→| GET | `/admin/search/settings` | Search settings - synonyms, stopwords, reindex | `Admin\Search\Settings` |
   143→
   144→---
   145→
   146→### 1.3 Storefront Routes
   147→
   148→**Middleware:** `web`, `store.resolve`
   149→
   150→These routes serve the public-facing storefront using Blade templates rendered through the active theme engine.
   151→
   152→| Method | Path | Description | Component / View |
   153→|--------|------|-------------|------------------|
   154→| GET | `/` | Home page | `Storefront\Home` |
   155→| GET | `/collections` | All collections listing | `Storefront\Collections\Index` |
   156→| GET | `/collections/{handle}` | Single collection page with product grid and filters | `Storefront\Collections\Show` |
   157→| GET | `/products/{handle}` | Product page - gallery, variant selector, add-to-cart | `Storefront\Products\Show` |
   158→| GET | `/cart` | Full cart page - line items, quantity controls, discount input, totals | `Storefront\Cart\Show` |
   159→| GET | `/search` | Search results page with filter panel, sort dropdown, product grid | `Storefront\Search\Index` |
   160→| GET | `/pages/{handle}` | Static content page (About, FAQ, etc.) | `Storefront\Pages\Show` |
   161→
   162→#### Customer Account Routes
   163→
   164→**Additional middleware:** `auth:customer`
   165→
   166→| Method | Path | Description | Component |
   167→|--------|------|-------------|-----------|
   168→| GET | `/account` | Customer account overview | `Storefront\Account\Dashboard` |
   169→| GET | `/account/orders` | Order history list | `Storefront\Account\Orders\Index` |
   170→| GET | `/account/orders/{orderNumber}` | Order detail | `Storefront\Account\Orders\Show` |
   171→| GET | `/account/addresses` | Address book management | `Storefront\Account\Addresses\Index` |
   172→
   173→#### Customer Auth Routes (no auth required)
   174→
   175→| Method | Path | Description | Component |
   176→|--------|------|-------------|-----------|
   177→| GET | `/account/login` | Customer login page | `Storefront\Account\Auth\Login` |
   178→| GET | `/account/register` | Customer registration page | `Storefront\Account\Auth\Register` |
   179→| GET | `/forgot-password` | Forgot password page | `Storefront\Account\Auth\ForgotPassword` |
   180→| POST | `/forgot-password` | Send reset link | Handled by Livewire action |
   181→| GET | `/reset-password/{token}` | Reset password form | `Storefront\Account\Auth\ResetPassword` |
   182→| POST | `/reset-password` | Reset password | Handled by Livewire action |
   183→
   184→---
   185→
   186→### 1.4 Checkout Routes
   187→
   188→**Middleware:** `web`, `store.resolve`
   189→
   190→| Method | Path | Description | Component |
   191→|--------|------|-------------|-----------|
   192→| GET | `/checkout/{checkoutId}` | Checkout page - stepper UI (Contact -> Shipping -> Payment) | `Storefront\Checkout\Show` |
   193→| GET | `/checkout/{checkoutId}/confirmation` | Order confirmation page - order number, items, totals | `Storefront\Checkout\Confirmation` |
   194→
   195→---
   196→
   197→## 2. Storefront REST API
   198→
   199→**Prefix:** `/api/storefront/v1`
   200→**Middleware:** `store.resolve`, `throttle:api.storefront`
   201→**Content-Type:** `application/json`
   202→
   203→These endpoints power the interactive storefront experience (cart drawer, checkout flow, search autocomplete, analytics).
   204→
   205→---
   206→
   207→### 2.1 Cart Endpoints
   208→
   209→#### POST /api/storefront/v1/carts
   210→
   211→Create a new cart.
   212→
   213→- **Authentication:** None (guest) or session-based (logged-in customer)
   214→- **Rate limit:** `api.storefront` (120/min per IP)
   215→
   216→**Request body:**
   217→
   218→```json
   219→{
   220→  "currency": "EUR"
   221→}
   222→```
   223→
   224→| Field | Type | Required | Validation |
   225→|-------|------|----------|------------|
   226→| `currency` | string | No | Valid ISO 4217 code. Defaults to store's `default_currency`. |
   227→
   228→**Success response:** `201 Created`
   229→
   230→```json
   231→{
   232→  "id": 42,
   233→  "store_id": 1,
   234→  "customer_id": null,
   235→  "currency": "EUR",
   236→  "cart_version": 1,
   237→  "status": "active",
   238→  "lines": [],
   239→  "totals": {
   240→    "subtotal": 0,
   241→    "discount": 0,
   242→    "total": 0,
   243→    "currency": "EUR"
   244→  },
   245→  "created_at": "2026-02-11T10:00:00Z",
   246→  "updated_at": "2026-02-11T10:00:00Z"
   247→}
   248→```
   249→
   250→**Error responses:**
   251→- `422` - Validation error (invalid currency)
   252→
   253→---
   254→
   255→#### GET /api/storefront/v1/carts/{cartId}
   256→
   257→Retrieve a cart with all lines and computed totals.
   258→
   259→- **Authentication:** None (cart ID acts as token)
   260→- **Rate limit:** `api.storefront` (120/min per IP)
   261→
   262→**URL parameters:**
   263→
   264→| Parameter | Type | Required | Description |
   265→|-----------|------|----------|-------------|
   266→| `cartId` | integer | Yes | The cart identifier |
   267→
   268→**Success response:** `200 OK`
   269→
   270→```json
   271→{
   272→  "id": 42,
   273→  "store_id": 1,
   274→  "customer_id": null,
   275→  "currency": "EUR",
   276→  "cart_version": 3,
   277→  "status": "active",
   278→  "lines": [
   279→    {
   280→      "id": 201,
   281→      "variant_id": 101,
   282→      "product_title": "Classic T-Shirt",
   283→      "variant_title": "Blue / Medium",
   284→      "sku": "TSH-BLU-M",
   285→      "quantity": 2,
   286→      "unit_price_amount": 2500,
   287→      "line_subtotal_amount": 5000,
   288→      "line_discount_amount": 0,
   289→      "line_total_amount": 5000,
   290→      "image_url": "https://cdn.example.com/products/tshirt-blue.jpg",
   291→      "requires_shipping": true,
   292→      "available_quantity": 50
   293→    }
   294→  ],
   295→  "totals": {
   296→    "subtotal": 5000,
   297→    "discount": 0,
   298→    "total": 5000,
   299→    "currency": "EUR",
   300→    "line_count": 1,
   301→    "item_count": 2
   302→  },
   303→  "created_at": "2026-02-11T10:00:00Z",
   304→  "updated_at": "2026-02-11T10:05:00Z"
   305→}
   306→```
   307→
   308→**Error responses:**
   309→- `404` - Cart not found
   310→
   311→---
   312→
   313→#### POST /api/storefront/v1/carts/{cartId}/lines
   314→
   315→Add a line item to the cart.
   316→
   317→- **Authentication:** None
   318→- **Rate limit:** `api.storefront` (120/min per IP)
   319→
   320→**Request body:**
   321→
   322→```json
   323→{
   324→  "variant_id": 101,
   325→  "quantity": 2
   326→}
   327→```
   328→
   329→| Field | Type | Required | Validation |
   330→|-------|------|----------|------------|
   331→| `variant_id` | integer | Yes | Must exist, belong to an active product in the current store |
   332→| `quantity` | integer | Yes | Min: 1, max: 9999. Must not exceed available inventory when policy is `deny`. |
   333→
   334→**Success response:** `201 Created`
   335→
   336→Returns the full cart object (same schema as GET /carts/{cartId}).
   337→
   338→**Error responses:**
   339→- `404` - Cart not found
   340→- `409` - Cart version conflict (concurrent modification, retry with fresh cart_version)
   341→- `422` - Validation error
   342→
   343→```json
   344→{
   345→  "message": "The given data was invalid.",
   346→  "errors": {
   347→    "variant_id": ["The selected variant is out of stock."]
   348→  }
   349→}
   350→```
   351→
   352→---
   353→
   354→#### PUT /api/storefront/v1/carts/{cartId}/lines/{lineId}
   355→
   356→Update the quantity of a cart line.
   357→
   358→- **Authentication:** None
   359→- **Rate limit:** `api.storefront` (120/min per IP)
   360→
   361→**Request body:**
   362→
   363→```json
   364→{
   365→  "quantity": 3,
   366→  "cart_version": 3
   367→}
   368→```
   369→
   370→| Field | Type | Required | Validation |
   371→|-------|------|----------|------------|
   372→| `quantity` | integer | Yes | Min: 1, max: 9999. Must not exceed available inventory when policy is `deny`. |
   373→| `cart_version` | integer | Yes | Must match current cart version for optimistic concurrency. |
   374→
   375→**Success response:** `200 OK`
   376→
   377→Returns the full cart object with updated totals.
   378→
   379→**Error responses:**
   380→- `404` - Cart or line not found
   381→- `409` - Version conflict
   382→- `422` - Validation error (e.g., exceeds inventory)
   383→
   384→---
   385→
   386→#### DELETE /api/storefront/v1/carts/{cartId}/lines/{lineId}
   387→
   388→Remove a line item from the cart.
   389→
   390→- **Authentication:** None
   391→- **Rate limit:** `api.storefront` (120/min per IP)
   392→
   393→**Request body:**
   394→
   395→```json
   396→{
   397→  "cart_version": 3
   398→}
   399→```
   400→
   401→| Field | Type | Required | Validation |
   402→|-------|------|----------|------------|
   403→| `cart_version` | integer | Yes | Must match current cart version. |
   404→
   405→**Success response:** `200 OK`
   406→
   407→Returns the full cart object.
   408→
   409→**Error responses:**
   410→- `404` - Cart or line not found
   411→- `409` - Version conflict
   412→
   413→---
   414→
   415→### 2.2 Checkout Endpoints
   416→
   417→#### Checkout Flow Sequence Diagram
   418→
   419→```mermaid
   420→sequenceDiagram
   421→    participant C as Customer Browser
   422→    participant API as Storefront API
   423→    participant MockPSP as Mock Payment Provider
   424→
   425→    C->>API: POST /checkouts (cart_id, email)
   426→    API-->>C: 201 Checkout (status: started)
   427→
   428→    C->>API: PUT /checkouts/{id}/address
   429→    API-->>C: 200 Checkout (status: addressed, available_shipping_methods)
   430→
   431→    C->>API: PUT /checkouts/{id}/shipping-method
   432→    API-->>C: 200 Checkout (status: shipping_selected, totals with tax)
   433→
   434→    opt Apply discount
   435→        C->>API: POST /checkouts/{id}/apply-discount
   436→        API-->>C: 200 Checkout (totals recalculated)
   437→    end
   438→
   439→    C->>API: POST /checkouts/{id}/pay (payment_method + details)
   440→    API->>API: Reserve inventory
   441→    API->>MockPSP: charge(checkout, paymentMethodData)
   442→
   443→    alt Credit Card / PayPal (instant capture)
   444→        MockPSP-->>API: PaymentResult (success)
   445→        API->>API: Create order (paid), commit inventory
   446→        API-->>C: 200 (order confirmation data)
   447→    else Credit Card (decline)
   448→        MockPSP-->>API: PaymentResult (failed)
   449→        API->>API: Release reserved inventory
   450→        API-->>C: 422 (error: card_declined / insufficient_funds)
   451→    else Bank Transfer (deferred)
   452→        MockPSP-->>API: PaymentResult (pending)
   453→        API->>API: Create order (pending), inventory stays reserved
   454→        API-->>C: 200 (order confirmation + bank transfer instructions)
   455→    end
   456→```
   457→
   458→#### POST /api/storefront/v1/checkouts
   459→
   460→Create a checkout from a cart.
   461→
   462→- **Authentication:** None or session-based
   463→- **Rate limit:** `checkout` (10/min per session)
   464→
   465→**Request body:**
   466→
   467→```json
   468→{
   469→  "cart_id": 42,
   470→  "email": "customer@example.com"
   471→}
   472→```
   473→
   474→| Field | Type | Required | Validation |
   475→|-------|------|----------|------------|
   476→| `cart_id` | integer | Yes | Must reference an active cart with at least one line. |
   477→| `email` | string | Yes | Valid email address. |
   478→
   479→**Success response:** `201 Created`
   480→
   481→```json
   482→{
   483→  "id": 7,
   484→  "store_id": 1,
   485→  "cart_id": 42,
   486→  "customer_id": null,
   487→  "status": "started",
   488→  "email": "customer@example.com",
   489→  "shipping_address_json": null,
   490→  "billing_address_json": null,
   491→  "shipping_method_id": null,
   492→  "discount_code": null,
   493→  "lines": [
   494→    {
   495→      "variant_id": 101,
   496→      "product_title": "Classic T-Shirt",
   497→      "variant_title": "Blue / Medium",
   498→      "sku": "TSH-BLU-M",
   499→      "quantity": 2,
   500→      "unit_price_amount": 2500,

prompt: Read the file /Users/fabianwesner/Herd/shop/specs/02-API-ROUTES.md in chunks (500 lines at a time) and provide a comprehensive summary of ALL API routes including: method, URL, middleware, controller/handler, request/response formats, and validation rules. I need every detail for implementation.


prompt: Read the file /Users/fabianwesner/Herd/shop/specs/01-DATABASE-SCHEMA.md in chunks (500 lines at a time) and provide a comprehensive summary of ALL table definitions including: table names, column names with types, foreign keys, indexes, constraints, and enums. I need every detail for implementation.