Success. Updated the following files:
M specs/progress.md

@@ -35,3 +35,3 @@
 | Seed/test data | `specs/07-SEEDERS-AND-TEST-DATA.md` | partial | Seeders create Acme Fashion and Acme Electronics stores/domains/settings/admin access with checkout and notification defaults, 2 apps, 4 app installations, 4 OAuth clients, 4 OAuth tokens, 8 webhook subscriptions, 2 search settings rows, 25 indexed products, 60 analytics_daily rows, 210 analytics_events rows, 6 collections, 127 variants, 127 inventory rows, 206 variant option pivots, 2 themes, 6 theme files with local disk contents and metadata, 2 theme settings rows, 6 pages, 4 navigation menus, 21 navigation items including 6 child items, 2 tax settings rows, 2 shipping zones, 6 shipping rates, 6 discounts, the seeded customer, 1 default seeded customer address, and no product media/runtime carts. Order/payment seed data is still missing. |
-| Browser E2E plan | `specs/08-PLAYWRIGHT-E2E-PLAN.md` | partial | Manual Playwright MCP smoke coverage has verified storefront catalog browsing on `shop.test` and `acme-fashion.test`, product detail, add-to-cart, cart drawer/page, checkout through order completion, order confirmation, customer order list/detail, SQLite-backed search and search API JSON, analytics event ingestion API, DB-backed content pages, seeded nested navigation dropdowns, admin dashboard on desktop/mobile, admin analytics, admin app list/detail, admin developers token/webhook flows, admin product/collection/inventory/order/customer/discount/content/navigation/settings/theme/search pages, admin checkout/notification settings pages, admin product media section rendering, admin analytics CSV export, admin customer address creation, admin discount creation/editing/filtering, admin page creation, admin nested navigation rendering/item creation/save, admin theme editor preview, admin theme file list/editor, admin search reindexing, admin fulfillment creation/shipping/delivery, and auth flows without console warnings/errors on successful pages. Automated Pest browser coverage now checks storefront core pages, storefront account auth/reset pages including the customer root reset paths and account aliases, authenticated admin navigation/pages/settings/theme/search/apps/developers surfaces, Suite 2 admin authentication login/validation/redirect/logout/sidebar navigation interactions, Suite 7 storefront browsing, Suite 8 cart flow, Suite 9 checkout flow, Suite 10 customer account registration/login/orders/addresses/logout, Suite 11 inventory policy enforcement, and mobile storefront rendering for JavaScript errors. The remaining Spec 08 interaction/permutation browser suites are still incomplete. |
+| Browser E2E plan | `specs/08-PLAYWRIGHT-E2E-PLAN.md` | partial | Manual Playwright MCP smoke coverage has verified storefront catalog browsing on `shop.test` and `acme-fashion.test`, product detail, add-to-cart, cart drawer/page, checkout through order completion, order confirmation, customer order list/detail, SQLite-backed search and search API JSON, analytics event ingestion API, DB-backed content pages, seeded nested navigation dropdowns, admin dashboard on desktop/mobile, admin analytics, admin app list/detail, admin developers token/webhook flows, admin product/collection/inventory/order/customer/discount/content/navigation/settings/theme/search pages, admin checkout/notification settings pages, admin product media section rendering, admin analytics CSV export, admin customer address creation, admin discount creation/editing/filtering, admin page creation, admin nested navigation rendering/item creation/save, admin theme editor preview, admin theme file list/editor, admin search reindexing, admin fulfillment creation/shipping/delivery, and auth flows without console warnings/errors on successful pages. Automated Pest browser coverage now checks storefront core pages, storefront account auth/reset pages including the customer root reset paths and account aliases, authenticated admin navigation/pages/settings/theme/search/apps/developers surfaces, Suite 2 admin authentication login/validation/redirect/logout/sidebar navigation interactions, Suite 7 storefront browsing, Suite 8 cart flow, Suite 9 checkout flow, Suite 10 customer account registration/login/orders/addresses/logout, Suite 11 inventory policy enforcement, Suite 12 tenant isolation, and mobile storefront rendering for JavaScript errors. The remaining Spec 08 interaction/permutation browser suites are still incomplete. |
 | Roadmap phases | `specs/09-IMPLEMENTATION-ROADMAP.md` | in progress | Phase 1 foundation, Phase 2 catalog data/UI, Phase 3 theme/content/navigation data with storefront consumption, Phase 4 cart/checkout/pricing backend foundation, Phase 4 storefront cart/checkout UI through `payment_selected`, the Phase 4 cart/checkout REST API surface, cart-page estimates, Phase 5 order/payment backend foundation, Phase 5 refund/fulfillment services, Phase 5 storefront order completion/customer order surfaces, Phase 5 admin order management, Phase 5 order API surfaces, Phase 6 admin dashboard/customer/discount/content/settings/theme/navigation management, Phase 7 search, Phase 7 analytics, and Phase 7 apps/webhooks are implemented. Final verification and completion audit are next. |
@@ -469,2 +469,8 @@
 - 2026-05-04: `php -d memory_limit=512M vendor/bin/pest --compact` passed the full suite after Suite 11: 319 tests, 1846 assertions.
+- 2026-05-04: `mcp__laravel_boost__.search_docs` consulted Pest browser testing, host/session auth testing, and Laravel database testing docs before adding the Spec 08 tenant-isolation browser suite.
+- 2026-05-04: `php artisan make:test Browser/Storefront/TenantIsolationTest --pest --no-interaction` created the tenant-isolation browser test, which was moved into the Spec 08 browser directory structure.
+- 2026-05-04: `vendor/bin/pint --dirty --format agent` passed after adding the tenant-isolation browser suite.
+- 2026-05-04: `php artisan test --compact tests/Browser/Storefront/TenantIsolationTest.php` passed for the new Suite 12 tenant-isolation browser coverage: 5 tests, 37 assertions.
+- 2026-05-04: `php artisan test --compact tests/Browser/SmokeTest.php tests/Browser/Admin/AuthenticationTest.php tests/Browser/Storefront/BrowsingTest.php tests/Browser/Storefront/CartTest.php tests/Browser/Storefront/CheckoutTest.php tests/Browser/Storefront/CustomerAccountTest.php tests/Browser/Storefront/InventoryTest.php tests/Browser/Storefront/TenantIsolationTest.php` passed after adding Suite 12: 75 tests, 444 assertions.
+- 2026-05-04: `php -d memory_limit=512M vendor/bin/pest --compact` passed the full suite after Suite 12: 324 tests, 1883 assertions.

@@ -538,3 +544,3 @@
 - Laravel's SQLite `enum()` migrations generate database `CHECK` clauses in this app; `DatabaseConstraintTest` locks representative constraints across tenancy, roles, catalog, orders, tax settings, and exports.
-- Automated browser coverage now combines smoke coverage with Suite 2 admin-auth interactions, Suite 7 storefront browsing interactions, Suite 8 cart-flow interactions, Suite 9 checkout-flow interactions, Suite 10 customer-account interactions, and Suite 11 inventory-policy interactions: storefront catalog/content/search/cart, storefront account auth/reset pages, the full authenticated admin navigation surface, admin login validation, protected-route redirects, logout, sidebar navigation, mobile storefront rendering, seeded collection/product/search/static page browsing, sale/compare-at pricing, draft-product visibility guards, stock/backorder messaging, main-navigation clicks, add-to-cart, cart quantity updates/removal, multi-product carts, subtotal/total display, valid/invalid/expired/maxed/free-shipping/fixed discount code handling, domestic/international checkout shipping, checkout discount carryover, address validation, empty-cart checkout protection, credit-card/PayPal/bank-transfer order completion, payment decline handling, payment-method UI switching, customer registration validation, customer login validation, account auth redirects, customer order history/detail, address book create/update, customer logout, deny-policy sold-out blocking, continue-policy backorder add-to-cart, in-stock status display, and deny-policy cart stock-limit feedback.
+- Automated browser coverage now combines smoke coverage with Suite 2 admin-auth interactions, Suite 7 storefront browsing interactions, Suite 8 cart-flow interactions, Suite 9 checkout-flow interactions, Suite 10 customer-account interactions, Suite 11 inventory-policy interactions, and Suite 12 tenant-isolation interactions: storefront catalog/content/search/cart, storefront account auth/reset pages, the full authenticated admin navigation surface, admin login validation, protected-route redirects, logout, sidebar navigation, mobile storefront rendering, seeded collection/product/search/static page browsing, sale/compare-at pricing, draft-product visibility guards, stock/backorder messaging, main-navigation clicks, add-to-cart, cart quantity updates/removal, multi-product carts, subtotal/total display, valid/invalid/expired/maxed/free-shipping/fixed discount code handling, domestic/international checkout shipping, checkout discount carryover, address validation, empty-cart checkout protection, credit-card/PayPal/bank-transfer order completion, payment decline handling, payment-method UI switching, customer registration validation, customer login validation, account auth redirects, customer order history/detail, address book create/update, customer logout, deny-policy sold-out blocking, continue-policy backorder add-to-cart, in-stock status display, deny-policy cart stock-limit feedback, storefront product/collection/search store isolation, admin product/order store isolation, and customer account store isolation.
 - Pest's per-visit browser `host` option is temporary, so host-sensitive storefront browser tests set the Playwright host for the whole test lifecycle to keep clicked absolute URLs resolving through `shop.test`.
@@ -549,3 +555,3 @@

-- Full automated browser interaction/permutation suites from Spec 08 are still incomplete beyond the expanded Pest browser smoke coverage, Suite 2 admin authentication, Suite 7 storefront browsing, Suite 8 cart flow, Suite 9 checkout flow, Suite 10 customer account flow, and Suite 11 inventory enforcement.
+- Full automated browser interaction/permutation suites from Spec 08 are still incomplete beyond the expanded Pest browser smoke coverage, Suite 2 admin authentication, Suite 7 storefront browsing, Suite 8 cart flow, Suite 9 checkout flow, Suite 10 customer account flow, Suite 11 inventory enforcement, and Suite 12 tenant isolation.
 - The full suite now needs a higher PHP memory limit when all browser suites run in one process; the default `php artisan test --compact` command hits 128 MB in Pest's browser WebSocket client, while `php -d memory_limit=512M vendor/bin/pest --compact` passes.
@@ -554,2 +560,2 @@

-Not complete. Phase 1 foundation, Phase 2 catalog data/UI surfaces, product media processing/admin upload controls, Phase 3 storefront theme/content/navigation data, the Phase 4 cart/checkout/pricing backend foundation, cart/checkout storefront UI through order completion, cart-page estimates, cart/checkout REST APIs, Phase 5 order/payment backend foundation, Phase 5 refund/fulfillment services, customer account order/address/logout views, admin dashboard/order/customer/discount/content/settings/theme-file/navigation/search/analytics/apps/developers management, storefront search and analytics APIs, product/customer/collection/discount/content page/store settings/search/analytics/shipping/tax/theme/order/export API surfaces, deferred OAuth/app route stubs, expanded automated browser smoke coverage, Suite 2 admin authentication browser coverage, Suite 7 storefront browsing browser coverage, Suite 8 cart-flow browser coverage, Suite 9 checkout-flow browser coverage, Suite 10 customer-account browser coverage, Suite 11 inventory-enforcement browser coverage, outbound webhook delivery foundations, spec-root customer password reset routes, and representative database CHECK constraint coverage are implemented, with the remaining full browser-suite gap tracked above.
+Not complete. Phase 1 foundation, Phase 2 catalog data/UI surfaces, product media processing/admin upload controls, Phase 3 storefront theme/content/navigation data, the Phase 4 cart/checkout/pricing backend foundation, cart/checkout storefront UI through order completion, cart-page estimates, cart/checkout REST APIs, Phase 5 order/payment backend foundation, Phase 5 refund/fulfillment services, customer account order/address/logout views, admin dashboard/order/customer/discount/content/settings/theme-file/navigation/search/analytics/apps/developers management, storefront search and analytics APIs, product/customer/collection/discount/content page/store settings/search/analytics/shipping/tax/theme/order/export API surfaces, deferred OAuth/app route stubs, expanded automated browser smoke coverage, Suite 2 admin authentication browser coverage, Suite 7 storefront browsing browser coverage, Suite 8 cart-flow browser coverage, Suite 9 checkout-flow browser coverage, Suite 10 customer-account browser coverage, Suite 11 inventory-enforcement browser coverage, Suite 12 tenant-isolation browser coverage, outbound webhook delivery foundations, spec-root customer password reset routes, and representative database CHECK constraint coverage are implemented, with the remaining full browser-suite gap tracked above.


Output: {"output":"Success. Updated the following files:\nM specs/progress.md\n","metadata":{"exit_code":0,"duration_seconds":0.0}}


/bin/zsh
-lc
git status --short && git diff --check

 M specs/progress.md
?? tests/Browser/Storefront/TenantIsolationTest.php


Output:
Chunk ID: c76943
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 19
Output:
 M specs/progress.md
?? tests/Browser/Storefront/TenantIsolationTest.php


git add tests/Browser/Storefront/TenantIsolationTest.php specs/progress.md && git diff --cached --stat && git commit -m "Add tenant isolation browser suite"


git add tests/Browser/Storefront/TenantIsolationTest.php specs/progress.md && git diff --cached --stat && git commit -m "Add tenant isolation browser suite"

 specs/progress.md                                |  14 +-
 tests/Browser/Storefront/TenantIsolationTest.php | 162 +++++++++++++++++++++++
 2 files changed, 172 insertions(+), 4 deletions(-)
[2026-05-04-codex-5-5-goal 93cc2d35] Add tenant isolation browser suite
 2 files changed, 172 insertions(+), 4 deletions(-)
 create mode 100644 tests/Browser/Storefront/TenantIsolationTest.php


Output:
Chunk ID: e75e11
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 96
Output:
 specs/progress.md                                |  14 +-
 tests/Browser/Storefront/TenantIsolationTest.php | 162 +++++++++++++++++++++++
 2 files changed, 172 insertions(+), 4 deletions(-)
[2026-05-04-codex-5-5-goal 93cc2d35] Add tenant isolation browser suite
 2 files changed, 172 insertions(+), 4 deletions(-)
 create mode 100644 tests/Browser/Storefront/TenantIsolationTest.php


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


Committed `93cc2d35` for Suite 12. The next suite is responsive/mobile. This one may need a small storefront layout addition because the spec expects a mobile menu control, and the current header only hides desktop nav on small screens.


Inspect Suite 13 responsive requirements and current responsive layout

Add missing mobile navigation/control if required

Explanation: Suite 12 is committed; Suite 13 exercises mobile/tablet layouts and interactions.


Output: Plan updated


livewire/flux livewire/livewire pestphp/pest tailwindcss

modal trigger responsive navigation mobile menu


sed -n '2520,2665p' specs/08-PLAYWRIGHT-E2E-PLAN.md


sed -n '1,160p' tests/Browser/SmokeTest.php && sed -n '1,150p' resources/views/layouts/storefront.blade.php


# Search Results

## Components Modal > $flux.modal()
Components Modal | livewire/flux@2.x | Source: https://fluxui.dev/components/modal#livewire/flux

$flux.modal()

Alpine.js magic method for controlling modals.

<ui-table-scroll-area>

Parameter

Description

    default|name

Name of the modal to control.

</ui-table-scroll-area>

<ui-table-scroll-area>

Method

Description

    show()

Shows the modal.

    close()

Closes the modal.

</ui-table-scroll-area>

---

## Upgrade Guide > Changes from v3 > Browser requirements
Upgrade Guide | tailwindcss@4.x | Source: https://github.com/tailwindlabs/tailwindcss.com/blob/main/src/docs/upgrade-guide.mdx

### Browser requirements

Tailwind CSS v4.0 is designed for modern browsers and targets Safari 16.4, Chrome 111, and Firefox 128. We depend on modern CSS features like `@property` and `color-mix()` for core framework features, and Tailwind CSS v4.0 will not work in older browsers.

If you need to support older browsers, we recommend sticking with v3.4 for now. We're actively exploring a compatibility mode to help people upgrade sooner that we hope to share more news on in the future.

---

## Translate > Examples > Responsive design
Translate | tailwindcss@4.x | Source: https://github.com/tailwindlabs/tailwindcss.com/blob/main/src/docs/translate.mdx

### Responsive design

<ResponsiveDesign
  element="img"
  elementAttributes={{ src: "/img/mountains.jpg" }}
  property="translate"
  defaultClass="translate-45"
  featuredClass="translate-60"
/>

---

## Testing Browser testing
Testing | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/testing

## Browser testing

Pest v4 includes first-party browser testing support powered by Playwright. This allows you to test your Livewire components in a real browser, interacting with them just like a user would.

### Installing browser testing

First, install the Pest browser plugin:

```shell
composer require pestphp/pest-plugin-browser --dev
```

Next, install Playwright via npm:

```shell
npm install playwright@latest
npx playwright install
```

For complete browser testing documentation, see the [Pest browser testing guide](https://pestphp.com/docs/browser-testing).

### Writing browser tests

Instead of using `Livewire::test()`, you can use `Livewire::visit()` to test your component in a real browser:

```php
it('can create a new post', function () {
    Livewire::visit('post.create')
        ->type('[wire\:model="title"]', 'My first post')
        ->type('[wire\:model="content"]', 'This is the content')
        ->press('Save')
        ->assertSee('Post created successfully');
});
```

Browser tests are slower than unit tests but provide end-to-end confidence that your components work as expected in a real browser environment.

For a complete list of available browser testing assertions, see the [Pest browser testing assertions](https://pestphp.com/docs/browser-testing#content-available-assertions).

> [!info] When to use browser tests
> Use browser tests for critical user flows and complex interactions. For most component testing, the standard `Livewire::test()` approach is faster and sufficient.

---

## Components Modal > flux:modal.trigger
Components Modal | livewire/flux@2.x | Source: https://fluxui.dev/components/modal#livewire/flux

flux:modal.trigger

<ui-table-scroll-area>

Prop

Description

    name

Name of the modal to trigger. Must match the modal's name.

    shortcut

Keyboard shortcut to open the modal (e.g., cmd.k).

</ui-table-scroll-area>

<ui-table-scroll-area>

Slot

Description

    default

The trigger element (e.g., button).

</ui-table-scroll-area>

---

## Compatibility Browser support
Compatibility | tailwindcss@4.x | Source: https://github.com/tailwindlabs/tailwindcss.com/blob/main/src/docs/compatibility.mdx

## Browser support

Tailwind CSS v4.0 is designed for and tested on modern browsers, and the core functionality of the framework specifically depends on these browser versions:

- **Chrome 111** _(released March 2023)_
- **Safari 16.4** _(released March 2023)_
- **Firefox 128** _(released July 2024)_

Tailwind also includes support for many bleeding-edge platform features like `field-sizing: content`, `@starting-style`, and `text-wrap: balance` that have limited browser support. It's up to you if you want to use these modern features in your projects — if the browsers you're targeting don't support them, simply don't use those utilities and variants.

If you're unsure about the support for a modern platform feature, the [Can I use](https://caniuse.com/mdn-css_at-rules_starting-style) database is a great resource.

---

## Components Modal > Flux::modals()
Components Modal | livewire/flux@2.x | Source: https://fluxui.dev/components/modal#livewire/flux

Flux::modals()

PHP method for controlling all modals on the page.

<ui-table-scroll-area>

Method

Description

    close()

Closes all modals on the page.

</ui-table-scroll-area>

---

## Skew > Examples > Responsive design
Skew | tailwindcss@4.x | Source: https://github.com/tailwindlabs/tailwindcss.com/blob/main/src/docs/skew.mdx

### Responsive design

<ResponsiveDesign
  element="img"
  elementAttributes={{ src: "/img/mountains.jpg" }}
  properties={["skewX()", "skewY()"]}
  defaultClass="skew-3"
  featuredClass="skew-12"
/>

---

## Display > Examples > Responsive design
Display | tailwindcss@4.x | Source: https://github.com/tailwindlabs/tailwindcss.com/blob/main/src/docs/display.mdx

### Responsive design

<ResponsiveDesign property="display" defaultClass="flex" featuredClass="inline-flex" />

---

## Components Modal > flux:modal.close
Components Modal | livewire/flux@2.x | Source: https://fluxui.dev/components/modal#livewire/flux

flux:modal.close

<ui-table-scroll-area>

Slot

Description

    default

The close trigger element (e.g., button).

</ui-table-scroll-area>

---

## Wire Transition Browser support
Wire Transition | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/wire-transition

## Browser support

View Transitions are supported in Chrome 111+, Edge 111+, and Safari 18+. In browsers that don't support View Transitions, elements will appear and disappear without animation—the functionality still works, just without the visual transition.

> [!warning] Firefox has limited support
> Firefox 144+ supports basic view transitions, but does not support transition types.

[View browser support on caniuse.com →](https://caniuse.com/view-transitions)

---

## Components Modal > Flux::modal()
Components Modal | livewire/flux@2.x | Source: https://fluxui.dev/components/modal#livewire/flux

Flux::modal()

PHP method for controlling modals from Livewire components.

<ui-table-scroll-area>

Parameter

Description

    default|name

Name of the modal to control.

</ui-table-scroll-area>

<ui-table-scroll-area>

Method

Description

    close()

Closes the modal.

</ui-table-scroll-area>

---

## Rotate > Examples > Responsive design
Rotate | tailwindcss@4.x | Source: https://github.com/tailwindlabs/tailwindcss.com/blob/main/src/docs/rotate.mdx

### Responsive design

<ResponsiveDesign
  element="img"
  elementAttributes={{ src: "/img/mountains.jpg" }}
  property="rotate"
  defaultClass="rotate-45"
  featuredClass="rotate-60"
/>

---

## Responsive Design Overview
Responsive Design | tailwindcss@4.x | Source: https://github.com/tailwindlabs/tailwindcss.com/blob/main/src/docs/responsive-design.mdx

## Overview

Every utility class in Tailwind can be applied conditionally at different breakpoints, which makes it a piece of cake to build complex responsive interfaces without ever leaving your HTML.

First, make sure you've added the [viewport meta tag](https://developer.mozilla.org/en-US/docs/Web/HTML/Viewport_meta_tag) to the `<head>` of your document:

```html
<!-- [!code filename:index.html] -->
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
```

Then to add a utility but only have it take effect at a certain breakpoint, all you need to do is prefix the utility with the breakpoint name, followed by the `:` character:

```html
<!-- [!code filename:HTML]  -->
<!-- [!code word:md\:w-32] -->
<!-- [!code word:lg\:w-48] -->
<!-- Width of 16 by default, 32 on medium screens, and 48 on large screens -->
<img class="w-16 md:w-32 lg:w-48" src="..." />
```

There are five breakpoints by default, inspired by common device resolutions:

{

      Breakpoint prefix
      Minimum width
      CSS

        sm

        40rem (640px)

        @media (width >= 40rem) { ... }

        md

        48rem (768px)

        @media (width >= 48rem) { ... }

        lg

        64rem (1024px)

        @media (width >= 64rem) { ... }

        xl

        80rem (1280px)

        @media (width >= 80rem) { ... }

        2xl

        96rem (1536px)

        @media (width >= 96rem) { ... }

}

This works for **every utility class in the framework**, which means you can change literally anything at a given breakpoint — even things like letter spacing or cursor styles.

Here's a simple example of a marketing page component that uses a stacked layout on small screens, and a side-by-side layout on larger screens:

<Example resizable padding={false}>
  {

Company retreats

                Incredible accommodation for your team

  }
</Example>

```html
<!-- [!code word:md\:max-w-2xl] -->
<div class="mx-auto max-w-md overflow-hidden rounded-xl bg-white shadow-md md:max-w-2xl">
  <!-- [!code word:md\:flex] -->
  <div class="md:flex">
    <!-- [!code word:md\:shrink-0] -->
    <div class="md:shrink-0">
      <!-- [!code word:md\:h-full] -->
      <!-- [!code word:md\:w-48] -->
      <img
        class="h-48 w-full object-cover md:h-full md:w-48"
        src="/img/building.jpg"
        alt="Modern building architecture"
      />
    </div>
    <div class="p-8">
      <div class="text-sm font-semibold tracking-wide text-indigo-500 uppercase">Company retreats</div>
      <a href="#" class="mt-1 block text-lg leading-tight font-medium text-black hover:underline">
        Incredible accommodation for your team
      </a>
      <p class="mt-2 text-gray-500">
        Looking to take your team away on a retreat to enjoy awesome food and take in some sunshine? We have a list of
        places to do just that.
      </p>
    </div>
  </div>
</div>
```

Here's how the example above works:

- By default, the outer `div` is `display: block`, but by adding the `md:flex` utility, it becomes `display: flex` on medium screens and larger.
- When the parent is a flex container, we want to make sure the image never shrinks, so we've added `md:shrink-0` to prevent shrinking on medium screens and larger. Technically we could have just used `shrink-0` since it would do nothing on smaller screens, but since it only matters on `md` screens, it's a good idea to make that clear in the class name.
- On small screens the image is automatically full width by default. On medium screens and up, we've constrained the width to a fixed size and ensured the image is full height using `md:h-full md:w-48`.

We've only used one breakpoint in this example, but you could easily customize this component at other sizes using the `sm`, `lg`, `xl`, or `2xl` responsive prefixes as well.

---

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

flux:modal

<ui-table-scroll-area>

Prop

Description

    name

Unique identifier for the modal. Required when using triggers.

    flyout

If true, the modal will open as a flyout.

    variant

Visual style of the modal. Options: default, floating, bare (legacy: flyout).

    position

For flyout modals, the direction they open from. Options: right (default), left, bottom.

    scroll

Scrolling behavior for long content. Options: body. When set to body, the entire viewport scrolls instead of clipping overflow.

    dismissible

If false, prevents closing the modal by clicking outside. Default: true.

    closable

If false, hides the close button. Default: true.

    wire:model

Optional Livewire property to bind the modal's open state to.

</ui-table-scroll-area>

<ui-table-scroll-area>

Event

Description

    close

Triggered when the modal is closed by any means.

    cancel

Triggered when the modal is closed by clicking outside or pressing escape.

</ui-table-scroll-area>

<ui-table-scroll-area>

Slot

Description

    default

The modal content.

</ui-table-scroll-area>

<ui-table-scroll-area>

Class

Description

    w-*

Common use: md:w-96 for width.

</ui-table-scroll-area>

---

## Responsive Design Working mobile-first
Responsive Design | tailwindcss@4.x | Source: https://github.com/tailwindlabs/tailwindcss.com/blob/main/src/docs/responsive-design.mdx

## Working mobile-first

Tailwind uses a mobile-first breakpoint system, similar to what you might be used to in other frameworks like Bootstrap.

What this means is that unprefixed utilities (like `uppercase`) take effect on all screen sizes, while prefixed utilities (like `md:uppercase`) only take effect at the specified breakpoint _and above_.

### Targeting mobile screens

Where this approach surprises people most often is that to style something for mobile, you need to use the unprefixed version of a utility, not the `sm:` prefixed version. Don't think of `sm:` as meaning "on small screens", think of it as "at the small _breakpoint_".

<TipBad>
  {
    <>
      Don't use sm: to target mobile devices
    </>
  }
</TipBad>

```html
<!-- [!code filename:HTML]  -->
<!-- This will only center text on screens 640px and wider, not on small screens -->
<div class="sm:text-center"></div>
```

<TipGood>{<>Use unprefixed utilities to target mobile, and override them at larger breakpoints</>}</TipGood>

```html
<!-- [!code filename:HTML]  -->
<!-- This will center text on mobile, and left align it on screens 640px and wider -->
<div class="text-center sm:text-left"></div>
```

For this reason, it's often a good idea to implement the mobile layout for a design first, then layer on any changes that make sense for `sm` screens, followed by `md` screens, etc.

### Targeting a breakpoint range

By default, styles applied by rules like `md:flex` will apply at that breakpoint and stay applied at larger breakpoints.

If you'd like to apply a utility _only_ when a specific breakpoint range is active, stack a responsive variant like `md` with a `max-*` variant to limit that style to a specific range:

```html
<!-- [!code filename:HTML]  -->
<div class="md:max-xl:flex">
  <!-- ... -->
</div>
```

Tailwind generates a corresponding `max-*` variant for each breakpoint, so out of the box the following variants are available:

{

      Variant
      Media query

        max-sm

        @media (width < 40rem) { ... }

        max-md

        @media (width < 48rem) { ... }

        max-lg

        @media (width < 64rem) { ... }

        max-xl

        @media (width < 80rem) { ... }

        max-2xl

        @media (width < 96rem) { ... }

}

### Targeting a single breakpoint

To target a single breakpoint, target the range for that breakpoint by stacking a responsive variant like `md` with the `max-*` variant for the next breakpoint:

```html
<!-- [!code filename:HTML]  -->
<div class="md:max-lg:flex">
  <!-- ... -->
</div>
```

Read about [targeting breakpoint ranges](#targeting-a-breakpoint-range) to learn more.

_[Truncated]_


Output:
Wall time: 3.5241 seconds
Output:
[{"type":"text","text":"# Search Results\n\n## Components Modal > $flux.modal()\nComponents Modal | livewire/flux@2.x | Source: https://fluxui.dev/components/modal#livewire/flux\n\n
$flux.modal()

Alpine.js magic method for controlling modals.

<ui-table-scroll-area>

Parameter

Description

\n    default|name\n

Name of the modal to control.

</ui-table-scroll-area>

<ui-table-scroll-area>

Method

Description

\n    show()\n

Shows the modal.

\n    close()\n

Closes the modal.

</ui-table-scroll-area>

\n\n---\n\n## Upgrade Guide > Changes from v3 > Browser requirements\nUpgrade Guide | tailwindcss@4.x | Source: https://github.com/tailwindlabs/tailwindcss.com/blob/main/src/docs/upgrade-guide.mdx\n\n### Browser requirements\n\nTailwind CSS v4.0 is designed for modern browsers and targets Safari 16.4, Chrome 111, and Firefox 128. We depend on modern CSS features like `@property` and `color-mix()` for core framework features, and Tailwind CSS v4.0 will not work in older browsers.\n\nIf you need to support older browsers, we recommend sticking with v3.4 for now. We're actively exploring a compatibility mode to help people upgrade sooner that we hope to share more news on in the future.\n\n---\n\n## Translate > Examples > Responsive design\nTranslate | tailwindcss@4.x | Source: https://github.com/tailwindlabs/tailwindcss.com/blob/main/src/docs/translate.mdx\n\n### Responsive design\n\n<ResponsiveDesign\n  element=\"img\"\n  elementAttributes={{ src: \"/img/mountains.jpg\" }}\n  property=\"translate\"\n  defaultClass=\"translate-45\"\n  featuredClass=\"translate-60\"\n/>\n\n---\n\n## Testing Browser testing\nTesting | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/testing\n\n## Browser testing\n\nPest v4 includes first-party browser testing support powered by Playwright. This allows you to test your Livewire components in a real browser, interacting with them just like a user would.\n\n### Installing browser testing\n\nFirst, install the Pest browser plugin:\n\n```shell\ncomposer require pestphp/pest-plugin-browser --dev\n```\n\nNext, install Playwright via npm:\n\n```shell\nnpm install playwright@latest\nnpx playwright install\n```\n\nFor complete browser testing documentation, see the [Pest browser testing guide](https://pestphp.com/docs/browser-testing).\n\n### Writing browser tests\n\nInstead of using `Livewire::test()`, you can use `Livewire::visit()` to test your component in a real browser:\n\n```php\nit('can create a new post', function () {\n    Livewire::visit('post.create')\n        ->type('[wire\\:model=\"title\"]', 'My first post')\n        ->type('[wire\\:model=\"content\"]', 'This is the content')\n        ->press('Save')\n        ->assertSee('Post created successfully');\n});\n```\n\nBrowser tests are slower than unit tests but provide end-to-end confidence that your components work as expected in a real browser environment.\n\nFor a complete list of available browser testing assertions, see the [Pest browser testing assertions](https://pestphp.com/docs/browser-testing#content-available-assertions).\n\n> [!info] When to use browser tests\n> Use browser tests for critical user flows and complex interactions. For most component testing, the standard `Livewire::test()` approach is faster and sufficient.\n\n---\n\n## Components Modal > flux:modal.trigger\nComponents Modal | livewire/flux@2.x | Source: https://fluxui.dev/components/modal#livewire/flux\n\n
flux:modal.trigger

<ui-table-scroll-area>

Prop

Description

\n    name\n

Name of the modal to trigger. Must match the modal's name.

\n    shortcut\n

Keyboard shortcut to open the modal (e.g., cmd.k).

</ui-table-scroll-area>

<ui-table-scroll-area>

Slot

Description

\n    default\n

The trigger element (e.g., button).

</ui-table-scroll-area>

\n\n---\n\n## Compatibility Browser support\nCompatibility | tailwindcss@4.x | Source: https://github.com/tailwindlabs/tailwindcss.com/blob/main/src/docs/compatibility.mdx\n\n## Browser support\n\nTailwind CSS v4.0 is designed for and tested on modern browsers, and the core functionality of the framework specifically depends on these browser versions:\n\n- **Chrome 111** _(released March 2023)_\n- **Safari 16.4** _(released March 2023)_\n- **Firefox 128** _(released July 2024)_\n\nTailwind also includes support for many bleeding-edge platform features like `field-sizing: content`, `@starting-style`, and `text-wrap: balance` that have limited browser support. It's up to you if you want to use these modern features in your projects — if the browsers you're targeting don't support them, simply don't use those utilities and variants.\n\nIf you're unsure about the support for a modern platform feature, the [Can I use](https://caniuse.com/mdn-css_at-rules_starting-style) database is a great resource.\n\n\n\n---\n\n## Components Modal > Flux::modals()\nComponents Modal | livewire/flux@2.x | Source: https://fluxui.dev/components/modal#livewire/flux\n\n
Flux::modals()

PHP method for controlling all modals on the page.

<ui-table-scroll-area>

Method

Description

\n    close()\n

Closes all modals on the page.

</ui-table-scroll-area>

\n\n---\n\n## Skew > Examples > Responsive design\nSkew | tailwindcss@4.x | Source: https://github.com/tailwindlabs/tailwindcss.com/blob/main/src/docs/skew.mdx\n\n### Responsive design\n\n<ResponsiveDesign\n  element=\"img\"\n  elementAttributes={{ src: \"/img/mountains.jpg\" }}\n  properties={[\"skewX()\", \"skewY()\"]}\n  defaultClass=\"skew-3\"\n  featuredClass=\"skew-12\"\n/>\n\n---\n\n## Display > Examples > Responsive design\nDisplay | tailwindcss@4.x | Source: https://github.com/tailwindlabs/tailwindcss.com/blob/main/src/docs/display.mdx\n\n### Responsive design\n\n<ResponsiveDesign property=\"display\" defaultClass=\"flex\" featuredClass=\"inline-flex\" />\n\n---\n\n## Components Modal > flux:modal.close\nComponents Modal | livewire/flux@2.x | Source: https://fluxui.dev/components/modal#livewire/flux\n\n
flux:modal.close

<ui-table-scroll-area>

Slot

Description

\n    default\n

The close trigger element (e.g., button).

</ui-table-scroll-area>

\n\n---\n\n## Wire Transition Browser support\nWire Transition | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/wire-transition\n\n## Browser support\n\nView Transitions are supported in Chrome 111+, Edge 111+, and Safari 18+. In browsers that don't support View Transitions, elements will appear and disappear without animation—the functionality still works, just without the visual transition.\n\n> [!warning] Firefox has limited support\n> Firefox 144+ supports basic view transitions, but does not support transition types.\n\n[View browser support on caniuse.com →](https://caniuse.com/view-transitions)\n\n---\n\n## Components Modal > Flux::modal()\nComponents Modal | livewire/flux@2.x | Source: https://fluxui.dev/components/modal#livewire/flux\n\n
Flux::modal()

PHP method for controlling modals from Livewire components.

<ui-table-scroll-area>

Parameter

Description

\n    default|name\n

Name of the modal to control.

</ui-table-scroll-area>

<ui-table-scroll-area>

Method

Description

\n    close()\n

Closes the modal.

</ui-table-scroll-area>

\n\n---\n\n## Rotate > Examples > Responsive design\nRotate | tailwindcss@4.x | Source: https://github.com/tailwindlabs/tailwindcss.com/blob/main/src/docs/rotate.mdx\n\n### Responsive design\n\n<ResponsiveDesign\n  element=\"img\"\n  elementAttributes={{ src: \"/img/mountains.jpg\" }}\n  property=\"rotate\"\n  defaultClass=\"rotate-45\"\n  featuredClass=\"rotate-60\"\n/>\n\n---\n\n## Responsive Design Overview\nResponsive Design | tailwindcss@4.x | Source: https://github.com/tailwindlabs/tailwindcss.com/blob/main/src/docs/responsive-design.mdx\n\n## Overview\n\nEvery utility class in Tailwind can be applied conditionally at different breakpoints, which makes it a piece of cake to build complex responsive interfaces without ever leaving your HTML.\n\nFirst, make sure you've added the [viewport meta tag](https://developer.mozilla.org/en-US/docs/Web/HTML/Viewport_meta_tag) to the `<head>` of your document:\n\n```html\n<!-- [!code filename:index.html] -->\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n```\n\nThen to add a utility but only have it take effect at a certain breakpoint, all you need to do is prefix the utility with the breakpoint name, followed by the `:` character:\n\n```html\n<!-- [!code filename:HTML]  -->\n<!-- [!code word:md\\:w-32] -->\n<!-- [!code word:lg\\:w-48] -->\n<!-- Width of 16 by default, 32 on medium screens, and 48 on large screens -->\n<img class=\"w-16 md:w-32 lg:w-48\" src=\"...\" />\n```\n\nThere are five breakpoints by default, inspired by common device resolutions:\n\n{\n\n
\n
\n
\n      Breakpoint prefix	\n      Minimum width	\n      CSS	\n
\n
\n
\n
\n      \n        sm\n      	\n      \n        40rem (640px)\n      	\n      \n        @media (width >= 40rem) { ... }\n      	\n
\n
\n      \n        md\n      	\n      \n        48rem (768px)\n      	\n      \n        @media (width >= 48rem) { ... }\n      	\n
\n
\n      \n        lg\n      	\n      \n        64rem (1024px)\n      	\n      \n        @media (width >= 64rem) { ... }\n      	\n
\n
\n      \n        xl\n      	\n      \n        80rem (1280px)\n      	\n      \n        @media (width >= 80rem) { ... }\n      	\n
\n
\n      \n        2xl\n      	\n      \n        96rem (1536px)\n      	\n      \n        @media (width >= 96rem) { ... }\n      	\n
\n
\n
\n}\n\nThis works for **every utility class in the framework**, which means you can change literally anything at a given breakpoint — even things like letter spacing or cursor styles.\n\nHere's a simple example of a marketing page component that uses a stacked layout on small screens, and a side-by-side layout on larger screens:\n\n
\n\n<Example resizable padding={false}>\n  {\n    \n
\n
\n
\n
\n              \n
\n
\n
Company retreats
\n              \n                Incredible accommodation for your team\n              \n              <p\n               \n                children={`Looking to take your team away on a retreat to enjoy awesome food\n              and take in some sunshine? We have a list of places to do just\n              that.`}\n              >
\n
\n
\n
\n
\n    \n  }\n</Example>\n\n```html\n<!-- [!code word:md\\:max-w-2xl] -->\n<div class=\"mx-auto max-w-md overflow-hidden rounded-xl bg-white shadow-md md:max-w-2xl\">\n  <!-- [!code word:md\\:flex] -->\n  <div class=\"md:flex\">\n    <!-- [!code word:md\\:shrink-0] -->\n    <div class=\"md:shrink-0\">\n      <!-- [!code word:md\\:h-full] -->\n      <!-- [!code word:md\\:w-48] -->\n      <img\n        class=\"h-48 w-full object-cover md:h-full md:w-48\"\n        src=\"/img/building.jpg\"\n        alt=\"Modern building architecture\"\n      />\n    </div>\n    <div class=\"p-8\">\n      <div class=\"text-sm font-semibold tracking-wide text-indigo-500 uppercase\">Company retreats</div>\n      <a href=\"#\" class=\"mt-1 block text-lg leading-tight font-medium text-black hover:underline\">\n        Incredible accommodation for your team\n      </a>\n      <p class=\"mt-2 text-gray-500\">\n        Looking to take your team away on a retreat to enjoy awesome food and take in some sunshine? We have a list of\n        places to do just that.\n      </p>\n    </div>\n  </div>\n</div>\n```\n\n
\n\nHere's how the example above works:\n\n- By default, the outer `div` is `display: block`, but by adding the `md:flex` utility, it becomes `display: flex` on medium screens and larger.\n- When the parent is a flex container, we want to make sure the image never shrinks, so we've added `md:shrink-0` to prevent shrinking on medium screens and larger. Technically we could have just used `shrink-0` since it would do nothing on smaller screens, but since it only matters on `md` screens, it's a good idea to make that clear in the class name.\n- On small screens the image is automatically full width by default. On medium screens and up, we've constrained the width to a fixed size and ensured the image is full height using `md:h-full md:w-48`.\n\nWe've only used one breakpoint in this example, but you could easily customize this component at other sizes using the `sm`, `lg`, `xl`, or `2xl` responsive prefixes as well.\n\n\n\n---\n\n## Components Modal > flux:modal\nComponents Modal | livewire/flux@2.x | Source: https://fluxui.dev/components/modal#livewire/flux\n\n
flux:modal

<ui-table-scroll-area>

Prop

Description

\n    name\n

Unique identifier for the modal. Required when using triggers.

\n    flyout\n

If true, the modal will open as a flyout.

\n    variant\n

Visual style of the modal. Options: default, floating, bare (legacy: flyout).

\n    position\n

For flyout modals, the direction they open from. Options: right (default), left, bottom.

\n    scroll\n

Scrolling behavior for long content. Options: body. When set to body, the entire viewport scrolls instead of clipping overflow.

\n    dismissible\n

If false, prevents closing the modal by clicking outside. Default: true.

\n    closable\n

If false, hides the close button. Default: true.

\n    wire:model\n

Optional Livewire property to bind the modal's open state to.

</ui-table-scroll-area>

<ui-table-scroll-area>

Event

Description

\n    close\n

Triggered when the modal is closed by any means.

\n    cancel\n

Triggered when the modal is closed by clicking outside or pressing escape.

</ui-table-scroll-area>

<ui-table-scroll-area>

Slot

Description

\n    default\n

The modal content.

</ui-table-scroll-area>

<ui-table-scroll-area>

Class

Description

\n    w-*\n

Common use: md:w-96 for width.

</ui-table-scroll-area>

\n\n---\n\n## Responsive Design Working mobile-first\nResponsive Design | tailwindcss@4.x | Source: https://github.com/tailwindlabs/tailwindcss.com/blob/main/src/docs/responsive-design.mdx\n\n## Working mobile-first\n\nTailwind uses a mobile-first breakpoint system, similar to what you might be used to in other frameworks like Bootstrap.\n\nWhat this means is that unprefixed utilities (like `uppercase`) take effect on all screen sizes, while prefixed utilities (like `md:uppercase`) only take effect at the specified breakpoint _and above_.\n\n### Targeting mobile screens\n\nWhere this approach surprises people most often is that to style something for mobile, you need to use the unprefixed version of a utility, not the `sm:` prefixed version. Don't think of `sm:` as meaning \"on small screens\", think of it as \"at the small _breakpoint_\".\n\n<TipBad>\n  {\n    <>\n      Don't use sm: to target mobile devices\n    </>\n  }\n</TipBad>\n\n```html\n<!-- [!code filename:HTML]  -->\n<!-- This will only center text on screens 640px and wider, not on small screens -->\n<div class=\"sm:text-center\"></div>\n```\n\n<TipGood>{<>Use unprefixed utilities to target mobile, and override them at larger breakpoints</>}</TipGood>\n\n```html\n<!-- [!code filename:HTML]  -->\n<!-- This will center text on mobile, and left align it on screens 640px and wider -->\n<div class=\"text-center sm:text-left\"></div>\n```\n\nFor this reason, it's often a good idea to implement the mobile layout for a design first, then layer on any changes that make sense for `sm` screens, followed by `md` screens, etc.\n\n### Targeting a breakpoint range\n\nBy default, styles applied by rules like `md:flex` will apply at that breakpoint and stay applied at larger breakpoints.\n\nIf you'd like to apply a utility _only_ when a specific breakpoint range is active, stack a responsive variant like `md` with a `max-*` variant to limit that style to a specific range:\n\n```html\n<!-- [!code filename:HTML]  -->\n<div class=\"md:max-xl:flex\">\n  <!-- ... -->\n</div>\n```\n\nTailwind generates a corresponding `max-*` variant for each breakpoint, so out of the box the following variants are available:\n\n{\n\n
\n
\n
\n      Variant	\n      Media query	\n
\n
\n
\n
\n      \n        max-sm\n      	\n      \n        @media (width < 40rem) { ... }\n      	\n
\n
\n      \n        max-md\n      	\n      \n        @media (width < 48rem) { ... }\n      	\n
\n
\n      \n        max-lg\n      	\n      \n        @media (width < 64rem) { ... }\n      	\n
\n
\n      \n        max-xl\n      	\n      \n        @media (width < 80rem) { ... }\n      	\n
\n
\n      \n        max-2xl\n      	\n      \n        @media (width < 96rem) { ... }\n      	\n
\n
\n
\n}\n\n### Targeting a single breakpoint\n\nTo target a single breakpoint, target the range for that breakpoint by stacking a responsive variant like `md` with the `max-*` variant for the next breakpoint:\n\n```html\n<!-- [!code filename:HTML]  -->\n<div class=\"md:max-lg:flex\">\n  <!-- ... -->\n</div>\n```\n\nRead about [targeting breakpoint ranges](#targeting-a-breakpoint-range) to learn more.\n\n\n\n_[Truncated]_"}]


sed -n '2520,2665p' specs/08-PLAYWRIGHT-E2E-PLAN.md

### Viewport Definitions

| Device | Width | Height | Use For |
|--------|-------|--------|---------|
| iPhone (mobile) | 375 | 812 | Storefront tests |
| iPad (tablet) | 768 | 1024 | Admin panel tests |

---

### Test 13.1: Storefront home works on mobile viewport

**Preconditions:** Seeded store

**Steps:**
1. Set viewport to iPhone dimensions (375x812)
2. Navigate to `/`

**Expected Results:**
- Page displays "Acme Fashion"
- Mobile menu (hamburger) is visible
- Desktop navigation is hidden
- No horizontal scroll
- No JavaScript errors

---

### Test 13.2: Product page stacks layout on mobile

**Preconditions:** Product #1

**Steps:**
1. Set viewport to mobile (375x812)
2. Navigate to `/products/classic-cotton-t-shirt`

**Expected Results:**
- Page displays "Classic Cotton T-Shirt", "24.99", "Add to cart"
- Gallery and details stack vertically
- No JavaScript errors

---

### Test 13.3: Can add to cart on mobile

**Preconditions:** Product #1

**Steps:**
1. Set viewport to mobile (375x812)
2. Navigate to `/products/classic-cotton-t-shirt`
3. Click "M" to select size M
4. Click "Black" to select color Black
5. Click "Add to cart"

**Expected Results:**
- Product added to cart successfully
- No JavaScript errors

---

### Test 13.4: Cart page works on mobile

**Preconditions:** Product #1

**Steps:**
1. Set viewport to mobile (375x812)
2. Navigate to `/products/classic-cotton-t-shirt`
3. Click "M" to select size M
4. Click "Black" to select color Black
5. Click "Add to cart"
6. Navigate to `/cart`

**Expected Results:**
- Page displays "Classic Cotton T-Shirt"
- "Checkout" button is visible and accessible
- No JavaScript errors

---

### Test 13.5: Checkout flow works on mobile

**Preconditions:** Product #1, Domestic shipping zone

**Steps:**
1. Set viewport to mobile (375x812)
2. Navigate to `/products/classic-cotton-t-shirt`
3. Click "M" to select size M
4. Click "Black" to select color Black
5. Click "Add to cart"
6. Navigate to `/cart`
7. Click "Checkout"
8. Enter email: `mobile@example.com`
9. Click "Continue"
10. Enter first name: `Mobile`
11. Enter last name: `User`
12. Enter address: `Mobile Str 1`
13. Enter city: `Berlin`
14. Enter postal code: `10115`
15. Enter country: `DE`
16. Click "Continue"

**Expected Results:**
- Page displays "Standard Shipping"
- All steps accessible without horizontal scrolling
- No JavaScript errors

---

### Test 13.6: Admin login works on tablet viewport

**Preconditions:** Seeded admin user

**Steps:**
1. Set viewport to iPad dimensions (768x1024)
2. Navigate to `/admin/login`
3. Enter email: `admin@acme.test`
4. Enter password: `password`
5. Click "Sign in"

**Expected Results:**
- Page displays "Dashboard"
- No JavaScript errors

---

### Test 13.7: Admin sidebar navigation works on tablet

**Preconditions:** Seeded admin user

**Steps:**
1. Set viewport to iPad dimensions (768x1024)
2. Navigate to `/admin/login`
3. Log in as admin (email: `admin@acme.test`, password: `password`)
4. Click "Products" in the sidebar
5. Verify "Products" heading is visible
6. Click "Orders" in the sidebar
7. Verify "Orders" heading is visible

**Expected Results:**
- Each section loads correctly
- No JavaScript errors

---

### Test 13.8: Collection page works on mobile with filters

**Preconditions:** Seeded collections


Output:
Chunk ID: 3bd57f
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 827
Output:
### Viewport Definitions

| Device | Width | Height | Use For |
|--------|-------|--------|---------|
| iPhone (mobile) | 375 | 812 | Storefront tests |
| iPad (tablet) | 768 | 1024 | Admin panel tests |

---

### Test 13.1: Storefront home works on mobile viewport

**Preconditions:** Seeded store

**Steps:**
1. Set viewport to iPhone dimensions (375x812)
2. Navigate to `/`

**Expected Results:**
- Page displays "Acme Fashion"
- Mobile menu (hamburger) is visible
- Desktop navigation is hidden
- No horizontal scroll
- No JavaScript errors

---

### Test 13.2: Product page stacks layout on mobile

**Preconditions:** Product #1

**Steps:**
1. Set viewport to mobile (375x812)
2. Navigate to `/products/classic-cotton-t-shirt`

**Expected Results:**
- Page displays "Classic Cotton T-Shirt", "24.99", "Add to cart"
- Gallery and details stack vertically
- No JavaScript errors

---

### Test 13.3: Can add to cart on mobile

**Preconditions:** Product #1

**Steps:**
1. Set viewport to mobile (375x812)
2. Navigate to `/products/classic-cotton-t-shirt`
3. Click "M" to select size M
4. Click "Black" to select color Black
5. Click "Add to cart"

**Expected Results:**
- Product added to cart successfully
- No JavaScript errors

---

### Test 13.4: Cart page works on mobile

**Preconditions:** Product #1

**Steps:**
1. Set viewport to mobile (375x812)
2. Navigate to `/products/classic-cotton-t-shirt`
3. Click "M" to select size M
4. Click "Black" to select color Black
5. Click "Add to cart"
6. Navigate to `/cart`

**Expected Results:**
- Page displays "Classic Cotton T-Shirt"
- "Checkout" button is visible and accessible
- No JavaScript errors

---

### Test 13.5: Checkout flow works on mobile

**Preconditions:** Product #1, Domestic shipping zone

**Steps:**
1. Set viewport to mobile (375x812)
2. Navigate to `/products/classic-cotton-t-shirt`
3. Click "M" to select size M
4. Click "Black" to select color Black
5. Click "Add to cart"
6. Navigate to `/cart`
7. Click "Checkout"
8. Enter email: `mobile@example.com`
9. Click "Continue"
10. Enter first name: `Mobile`
11. Enter last name: `User`
12. Enter address: `Mobile Str 1`
13. Enter city: `Berlin`
14. Enter postal code: `10115`
15. Enter country: `DE`
16. Click "Continue"

**Expected Results:**
- Page displays "Standard Shipping"
- All steps accessible without horizontal scrolling
- No JavaScript errors

---

### Test 13.6: Admin login works on tablet viewport

**Preconditions:** Seeded admin user

**Steps:**
1. Set viewport to iPad dimensions (768x1024)
2. Navigate to `/admin/login`
3. Enter email: `admin@acme.test`
4. Enter password: `password`
5. Click "Sign in"

**Expected Results:**
- Page displays "Dashboard"
- No JavaScript errors

---

### Test 13.7: Admin sidebar navigation works on tablet

**Preconditions:** Seeded admin user

**Steps:**
1. Set viewport to iPad dimensions (768x1024)
2. Navigate to `/admin/login`
3. Log in as admin (email: `admin@acme.test`, password: `password`)
4. Click "Products" in the sidebar
5. Verify "Products" heading is visible
6. Click "Orders" in the sidebar
7. Verify "Orders" heading is visible

**Expected Results:**
- Each section loads correctly
- No JavaScript errors

---

### Test 13.8: Collection page works on mobile with filters

**Preconditions:** Seeded collections


sed -n '1,160p' tests/Browser/SmokeTest.php && sed -n '1,150p' resources/views/layouts/storefront.blade.php

storefront.blade.php

<?php

use App\Models\Collection as ProductCollection;
use App\Models\Page;
use App\Models\Product;
use App\Models\Store;
use App\Models\User;
use Database\Seeders\DatabaseSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;

uses(RefreshDatabase::class);

beforeEach(function (): void {
    $this->seed(DatabaseSeeder::class);
});

function browserSmokeStore(): Store
{
    return Store::query()->where('handle', 'acme-fashion')->firstOrFail();
}

function browserSmokeHost(): array
{
    return ['host' => 'shop.test'];
}

test('storefront core pages render without javascript errors', function (): void {
    $store = browserSmokeStore();
    $product = Product::withoutGlobalScopes()
        ->where('store_id', $store->getKey())
        ->where('status', 'active')
        ->firstOrFail();
    $collection = ProductCollection::withoutGlobalScopes()
        ->where('store_id', $store->getKey())
        ->where('status', 'active')
        ->firstOrFail();
    $page = Page::withoutGlobalScopes()
        ->where('store_id', $store->getKey())
        ->where('status', 'published')
        ->firstOrFail();

    $pages = visit([
        '/',
        '/collections',
        "/collections/{$collection->handle}",
        "/products/{$product->handle}",
        '/search?q=shirt',
        "/pages/{$page->handle}",
        '/cart',
    ], browserSmokeHost());

    $pages->assertNoJavaScriptErrors();

    [$home, $collections, $collectionPage, $productPage, $search, $contentPage, $cart] = $pages;

    $home->assertSee($store->name);
    $collections->assertSee('Collections');
    $collectionPage->assertSee($collection->title);
    $productPage->assertSee($product->title);
    $search->assertSee('Search');
    $contentPage->assertSee($page->title);
    $cart->assertSee('Cart');
});

test('admin core pages render for an authenticated store user', function (): void {
    $store = browserSmokeStore();
    $user = User::query()->where('email', 'admin@acme.test')->firstOrFail();

    $this->actingAs($user);
    $this->withSession(['current_store_id' => $store->getKey()]);

    $expected = [
        '/admin' => 'Dashboard',
        '/admin/analytics' => 'Analytics',
        '/admin/apps' => 'Apps',
        '/admin/developers' => 'Developers',
        '/admin/products' => 'Products',
        '/admin/collections' => 'Collections',
        '/admin/inventory' => 'Inventory',
        '/admin/orders' => 'Orders',
        '/admin/customers' => 'Customers',
        '/admin/discounts' => 'Discounts',
        '/admin/pages' => 'Pages',
        '/admin/navigation' => 'Navigation',
        '/admin/themes' => 'Themes',
        '/admin/settings' => 'Settings',
        '/admin/settings/shipping' => 'Shipping',
        '/admin/settings/taxes' => 'Taxes',
        '/admin/settings/checkout' => 'Checkout',
        '/admin/settings/notifications' => 'Notifications',
        '/admin/search/settings' => 'Search',
    ];

    $pages = visit(array_keys($expected), browserSmokeHost());

    $pages->assertNoJavaScriptErrors();

    foreach ($pages as $index => $page) {
        $page->assertSee(array_values($expected)[$index]);
    }
});

test('storefront account auth pages render without javascript errors', function (): void {
    $pages = visit([
        '/account/login',
        '/account/register',
        '/forgot-password',
        '/reset-password/test-token?email=customer@example.test',
        '/account/forgot-password',
        '/account/reset-password/test-token?email=customer@example.test',
    ], browserSmokeHost());

    $pages->assertNoJavaScriptErrors();

    [$login, $register, $forgotPassword, $resetPassword, $accountForgotPassword, $accountResetPassword] = $pages;

    $login->assertSee('Log in');
    $register->assertSee('Create an account');
    $forgotPassword->assertSee('Reset password');
    $resetPassword->assertSee('New password');
    $accountForgotPassword->assertSee('Reset password');
    $accountResetPassword->assertSee('New password');
});

test('storefront home renders on a mobile viewport', function (): void {
    $store = browserSmokeStore();

    visit('/', browserSmokeHost())
        ->on()
        ->mobile()
        ->assertSee($store->name)
        ->assertNoJavaScriptErrors();
});
<!DOCTYPE html>
getLocale()) }}">

        @include('partials.head')

        @php($store = app()->bound('current_store') ? app('current_store') : null)
        @php($announcement = data_get($themeSettings ?? [], 'announcement', []))
        @php($mainLinks = ($mainNavigation ?? []) !== [] ? $mainNavigation : [['label' => 'Collections', 'url' => route('collections.index'), 'external' => false], ['label' => 'Search', 'url' => route('search.index'), 'external' => false]])
        @php($footerLinks = ($footerNavigation ?? []) !== [] ? $footerNavigation : [['label' => 'Collections',…1255 chars truncated…ticky', true),
        ])>

                    {{ $store?->name ?? config('app.name') }}

                    @foreach ($mainLinks as $item)
                        @if (($item['children'] ?? []) !== [])

                                    {{ $item['label'] }}
                                    <flux:icon name="chevron-down" class="size-4" />

                                    @foreach ($item['children'] as $child)

                                            {{ $child['label'] }}

                                    @endforeach

                        @else

                                {{ $item['label'] }}

                        @endif
                    @endforeach

                    <flux:button :href="route('search.index')" wire:navigate variant="subtle" icon="magnifying-glass" aria-label="Search" />
                    <flux:button :href="route('account.dashboard')" wire:navigate variant="subtle" icon="user" aria-label="Account" />
                    <flux:modal.trigger name="cart-drawer">
                        <flux:button variant="subtle" icon="shopping-bag" aria-label="Cart" />
                    </flux:modal.trigger>

        <livewire:storefront.cart-drawer />

            {{ $slot }}

{{ $store?->name ?? config('app.name') }}

{{ data_get($themeSettings ?? [], 'footer.tagline') }}

Shop

                        @foreach ($footerLinks as $item)

                                {{ $item['label'] }}

                            @foreach (($item['children'] ?? []) as $child)

                                    {{ $child['label'] }}

                            @endforeach
                        @endforeach

Customer

                        Account
                        About

        @fluxScripts


Output:
Chunk ID: 875a88
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 2814
Output:
<?php

use App\Models\Collection as ProductCollection;
use App\Models\Page;
use App\Models\Product;
use App\Models\Store;
use App\Models\User;
use Database\Seeders\DatabaseSeeder;
use Illuminate\Foundation\Testing\RefreshDatabase;

uses(RefreshDatabase::class);

beforeEach(function (): void {
    $this->seed(DatabaseSeeder::class);
});

function browserSmokeStore(): Store
{
    return Store::query()->where('handle', 'acme-fashion')->firstOrFail();
}

function browserSmokeHost(): array
{
    return ['host' => 'shop.test'];
}

test('storefront core pages render without javascript errors', function (): void {
    $store = browserSmokeStore();
    $product = Product::withoutGlobalScopes()
        ->where('store_id', $store->getKey())
        ->where('status', 'active')
        ->firstOrFail();
    $collection = ProductCollection::withoutGlobalScopes()
        ->where('store_id', $store->getKey())
        ->where('status', 'active')
        ->firstOrFail();
    $page = Page::withoutGlobalScopes()
        ->where('store_id', $store->getKey())
        ->where('status', 'published')
        ->firstOrFail();

    $pages = visit([
        '/',
        '/collections',
        "/collections/{$collection->handle}",
        "/products/{$product->handle}",
        '/search?q=shirt',
        "/pages/{$page->handle}",
        '/cart',
    ], browserSmokeHost());

    $pages->assertNoJavaScriptErrors();

    [$home, $collections, $collectionPage, $productPage, $search, $contentPage, $cart] = $pages;

    $home->assertSee($store->name);
    $collections->assertSee('Collections');
    $collectionPage->assertSee($collection->title);
    $productPage->assertSee($product->title);
    $search->assertSee('Search');
    $contentPage->assertSee($page->title);
    $cart->assertSee('Cart');
});

test('admin core pages render for an authenticated store user', function (): void {
    $store = browserSmokeStore();
    $user = User::query()->where('email', 'admin@acme.test')->firstOrFail();

    $this->actingAs($user);
    $this->withSession(['current_store_id' => $store->getKey()]);

    $expected = [
        '/admin' => 'Dashboard',
        '/admin/analytics' => 'Analytics',
        '/admin/apps' => 'Apps',
        '/admin/developers' => 'Developers',
        '/admin/products' => 'Products',
        '/admin/collections' => 'Collections',
        '/admin/inventory' => 'Inventory',
        '/admin/orders' => 'Orders',
        '/admin/customers' => 'Customers',
        '/admin/discounts' => 'Discounts',
        '/admin/pages' => 'Pages',
        '/admin/navigation' => 'Navigation',
        '/admin/themes' => 'Themes',
        '/admin/settings' => 'Settings',
        '/admin/settings/shipping' => 'Shipping',
        '/admin/settings/taxes' => 'Taxes',
        '/admin/settings/checkout' => 'Checkout',
        '/admin/settings/notifications' => 'Notifications',
        '/admin/search/settings' => 'Search',
    ];

    $pages = visit(array_keys($expected), browserSmokeHost());

    $pages->assertNoJavaScriptErrors();

    foreach ($pages as $index => $page) {
        $page->assertSee(array_values($expected)[$index]);
    }
});

test('storefront account auth pages render without javascript errors', function (): void {
    $pages = visit([
        '/account/login',
        '/account/register',
        '/forgot-password',
        '/reset-password/test-token?email=customer@example.test',
        '/account/forgot-password',
        '/account/reset-password/test-token?email=customer@example.test',
    ], browserSmokeHost());

    $pages->assertNoJavaScriptErrors();

    [$login, $register, $forgotPassword, $resetPassword, $accountForgotPassword, $accountResetPassword] = $pages;

    $login->assertSee('Log in');
    $register->assertSee('Create an account');
    $forgotPassword->assertSee('Reset password');
    $resetPassword->assertSee('New password');
    $accountForgotPassword->assertSee('Reset password');
    $accountResetPassword->assertSee('New password');
});

test('storefront home renders on a mobile viewport', function (): void {
    $store = browserSmokeStore();

    visit('/', browserSmokeHost())
        ->on()
        ->mobile()
        ->assertSee($store->name)
        ->assertNoJavaScriptErrors();
});
<!DOCTYPE html>
getLocale()) }}">

        @include('partials.head')

        @php($store = app()->bound('current_store') ? app('current_store') : null)
        @php($announcement = data_get($themeSettings ?? [], 'announcement', []))
        @php($mainLinks = ($mainNavigation ?? []) !== [] ? $mainNavigation : [['label' => 'Collections', 'url' => route('collections.index'), 'external' => false], ['label' => 'Search', 'url' => route('search.index'), 'external' => false]])
        @php($footerLinks = ($footerNavigation ?? []) !== [] ? $footerNavigation : [['label' => 'Collections', 'url' => route('collections.index'), 'external' => false], ['label' => 'Search', 'url' => route('search.index'), 'external' => false]])

            Skip to main content

        @if (data_get($announcement, 'enabled', false))

                @if (data_get($announcement, 'url'))

                        {{ data_get($announcement, 'text') }}

                @else
                    {{ data_get($announcement, 'text') }}
                @endif

        @endif

         data_get($themeSettings ?? [], 'header.sticky', true),
        ])>

                    {{ $store?->name ?? config('app.name') }}

                    @foreach ($mainLinks as $item)
                        @if (($item['children'] ?? []) !== [])

                                    {{ $item['label'] }}
                                    <flux:icon name="chevron-down" class="size-4" />

                                    @foreach ($item['children'] as $child)

                                            {{ $child['label'] }}

                                    @endforeach

                        @else

                                {{ $item['label'] }}

                        @endif
                    @endforeach

                    <flux:button :href="route('search.index')" wire:navigate variant="subtle" icon="magnifying-glass" aria-label="Search" />
                    <flux:button :href="route('account.dashboard')" wire:navigate variant="subtle" icon="user" aria-label="Account" />
                    <flux:modal.trigger name="cart-drawer">
                        <flux:button variant="subtle" icon="shopping-bag" aria-label="Cart" />
                    </flux:modal.trigger>

        <livewire:storefront.cart-drawer />

            {{ $slot }}

{{ $store?->name ?? config('app.name') }}

{{ data_get($themeSettings ?? [], 'footer.tagline') }}

Shop

                        @foreach ($footerLinks as $item)

                                {{ $item['label'] }}

                            @foreach (($item['children'] ?? []) as $child)

                                    {{ $child['label'] }}

                            @endforeach
                        @endforeach

Customer

                        Account
                        About

        @fluxScripts


<permissions instructions>
Filesystem sandboxing defines which files can be read or written. `sandbox_mode` is `danger-full-access`: No filesystem sandboxing - all commands are permitted. Network access is enabled.
Approval policy is currently never. Do not provide the `sandbox_permissions` for any reason, commands will be rejected.
</permissions instructions>

<skills_instructions>
## Skills
A skill is a set of local instructions to follow that is stored in a `SKILL.md` file. Below is the list of skills that can be used. Each entry includes a name, description, and file path so you can open the source for full instructions when using a specific skill.
### Available skills
- imagegen: Generate or edit raster images when the task benefits from AI-created bitmap visuals such as photos, illustrations, textures, sprites, mockups, or transparent-background cutouts. Use when Codex should create a brand-new image, transform an existing image, or derive visual variants from references, and the output should be a bitmap asset rather than repo-native code or vector. Do not use when the task is better handled by editing existing SVG/vector/code-native assets, extending an established icon or logo system, or building the visual directly in HTML/CSS/canvas. (file: /Users/fabianwesner/.codex/skills/.system/imagegen/SKILL.md)
- openai-docs: Use when the user asks how to build with OpenAI products or APIs and needs up-to-date official documentation with citations, help choosing the latest model for a use case, or model upgrade and prompt-upgrade guidance; prioritize OpenAI docs MCP tools, use bundled references only as helper context, and restrict any fallback browsing to official OpenAI domains. (file: /Users/fabianwesner/.codex/skills/.system/openai-docs/SKILL.md)
- plugin-creator: Create and scaffold plugin directories for Codex with a required `.codex-plugin/plugin.json`, optional plugin folders/files, and baseline placeholders you can edit before publishing or testing. Use when Codex needs to create a new local plugin, add optional plugin structure, or generate or update repo-root `.agents/plugins/marketplace.json` entries for plugin ordering and availability metadata. (file: /Users/fabianwesner/.codex/skills/.system/plugin-creator/SKILL.md)
- skill-creator: Guide for creating effective skills. This skill should be used when users want to create a new skill (or update an existing skill) that extends Codex's capabilities with specialized knowledge, workflows, or tool integrations. (file: /Users/fabianwesner/.codex/skills/.system/skill-creator/SKILL.md)
- skill-installer: Install Codex skills into $CODEX_HOME/skills from a curated list or a GitHub repo path. Use when a user asks to list installable skills, install a curated skill, or install a skill from another repo (including private repos). (file: /Users/fabianwesner/.codex/skills/.system/skill-installer/SKILL.md)
- developing-with-fortify: Laravel Fortify headless authentication backend development. Activate when implementing authentication features including login, registration, password reset, email verification, two-factor authentication (2FA/TOTP), profile updates, headless auth, authentication scaffolding, or auth guards in Laravel applications. (file: /Users/fabianwesner/Herd/shop/.agents/skills/developing-with-fortify/SKILL.md)
- fluxui-development: Use this skill for Flux UI development in Livewire applications only. Trigger when working with <flux:*> components, building or customizing Livewire component UIs, creating forms, modals, tables, or other interactive elements. Covers: flux: components (buttons, inputs, modals, forms, tables, date-pickers, kanban, badges, tooltips, etc.), component composition, Tailwind CSS styling, Heroicons/Lucide icon integration, validation patterns, responsive design, and theming. Do not use for non-Livewire frameworks or non-component styling. (file: /Users/fabianwesner/Herd/shop/.agents/skills/fluxui-development/SKILL.md)
- laravel-best-practices: Apply this skill whenever writing, reviewing, or refactoring Laravel PHP code. This includes creating or modifying controllers, models, migrations, form requests, policies, jobs, scheduled commands, service classes, and Eloquent queries. Triggers for N+1 and query performance issues, caching strategies, authorization and security patterns, validation, error handling, queue and job configuration, route definitions, and architectural decisions. Also use for Laravel code reviews and refactoring existing Laravel code to follow best practices. Covers any task involving Laravel backend PHP code patterns. (file: /Users/fabianwesner/Herd/shop/.agents/skills/laravel-best-practices/SKILL.md)
- livewire-development: Use for any task or question involving Livewire. Activate if user mentions Livewire, wire: directives, or Livewire-specific concepts like wire:model, wire:click, wire:sort, or islands, invoke this skill. Covers building new components, debugging reactivity issues, real-time form validation, drag-and-drop, loading states, migrating from Livewire 3 to 4, converting component formats (SFC/MFC/class-based), and performance optimization. Do not use for non-Livewire reactive UI (React, Vue, Alpine-only, Inertia.js) or standard Laravel forms without Livewire. (file: /Users/fabianwesner/Herd/shop/.agents/skills/livewire-development/SKILL.md)
- pest-testing: Use this skill for Pest PHP testing in Laravel projects only. Trigger whenever any test is being written, edited, fixed, or refactored — including fixing tests that broke after a code change, adding assertions, converting PHPUnit to Pest, adding datasets, and TDD workflows. Always activate when the user asks how to write something in Pest, mentions test files or directories (tests/Feature, tests/Unit, tests/Browser), or needs browser testing, smoke testing multiple pages for JS errors, or architecture tests. Covers: test()/it()/expect() syntax, datasets, mocking, browser testing (visit/click/fill), smoke testing, arch(), Livewire component tests, RefreshDatabase, and all Pest 4 features. Do not use for factories, seeders, migrations, controllers, models, or non-test PHP code. (file: /Users/fabianwesner/Herd/shop/.agents/skills/pest-testing/SKILL.md)
- tailwindcss-development: Always invoke when the user's message includes 'tailwind' in any form. Also invoke for: building responsive grid layouts (multi-column card grids, product grids), flex/grid page structures (dashboards with sidebars, fixed topbars, mobile-toggle navs), styling UI components (cards, tables, navbars, pricing sections, forms, inputs, badges), adding dark mode variants, fixing spacing or typography, and Tailwind v3/v4 work. The core use case: writing or fixing Tailwind utility classes in HTML templates (Blade, JSX, Vue). Skip for backend PHP logic, database queries, API routes, JavaScript with no HTML/CSS component, CSS file audits, build tool configuration, and vanilla CSS. (file: /Users/fabianwesner/Herd/shop/.agents/skills/tailwindcss-development/SKILL.md)
### How to use skills
- Discovery: The list above is the skills available in this session (name + description + file path). Skill bodies live on disk at the listed paths.
- Trigger rules: If the user names a skill (with `$SkillName` or plain text) OR the task clearly matches a skill's description shown above, you must use that skill for that turn. Multiple mentions mean use them all. Do not carry skills across turns unless re-mentioned.
- Missing/blocked: If a named skill isn't in the list or the path can't be read, say so briefly and continue with the best fallback.
- How to use a skill (progressive disclosure):
  1) After deciding to use a skill, open its `SKILL.md`. Read only enough to follow the workflow.
  2) When `SKILL.md` references relative paths (e.g., `scripts/foo.py`), resolve them relative to the skill directory listed above first, and only consider other paths if needed.
  3) If `SKILL.md` points to extra folders such as `references/`, load only the specific files needed for the request; don't bulk-load everything.
  4) If `scripts/` exist, prefer running or patching them instead of retyping large code blocks.
  5) If `assets/` or templates exist, reuse them instead of recreating from scratch.
- Coordination and sequencing:
  - If multiple skills apply, choose the minimal set that covers the request and state the order you'll use them.
  - Announce which skill(s) you're using and why (one short line). If you skip an obvious skill, say why.
- Context hygiene:
  - Keep context small: summarize long sections instead of pasting them; only load extra files when needed.
  - Avoid deep reference-chasing: prefer opening only files directly linked from `SKILL.md` unless you're blocked.
  - When variants exist (frameworks, providers, domains), pick only the relevant reference file(s) and note that choice.
- Safety and fallback: If a skill can't be applied cleanly (missing files, unclear instructions), state the issue, pick the next-best approach, and continue.
</skills_instructions>

# AGENTS.md instructions for /Users/fabianwesner/Herd/shop

<INSTRUCTIONS>
# Shop

Self-contained e-commerce platform with multi-tenant support, product catalog, cart, checkout, payments (mock PSP), order management, customer accounts, and an admin panel.

## Tech Stack

- PHP 8.4 / Laravel 12
- Livewire v4 / Flux UI Free v2
- Tailwind CSS v4
- SQLite
- Pest v4

## Specs

The complete specification is in `specs/`. Start with `specs/09-IMPLEMENTATION-ROADMAP.md` for the build order, then reference the other files as needed:

- `specs/01-DATABASE-SCHEMA.md` - Database schema
- `specs/02-API-ROUTES.md` - Routes and API endpoints
- `specs/03-ADMIN-UI.md` - Admin panel
- `specs/04-STOREFRONT-UI.md` - Storefront
- `specs/05-BUSINESS-LOGIC.md` - Business logic
- `specs/06-AUTH-AND-SECURITY.md` - Auth and security
- `specs/07-SEEDERS-AND-TEST-DATA.md` - Seeders and test data
- `specs/08-PLAYWRIGHT-E2E-PLAN.md` - E2E browser tests
- `specs/09-IMPLEMENTATION-ROADMAP.md` - Implementation roadmap

===

<laravel-boost-guidelines>
=== foundation rules ===

# Laravel Boost Guidelines

The Laravel Boost guidelines are specifically curated by Laravel maintainers for this application. These guidelines should be followed closely to ensure the best experience when building Laravel applications.

## Foundational Context

This application is a Laravel application and its main Laravel ecosystems package & versions are below. You are an expert with them all. Ensure you abide by these specific packages & versions.

- php - 8.4
- laravel/fortify (FORTIFY) - v1
- laravel/framework (LARAVEL) - v12
- laravel/prompts (PROMPTS) - v0
- livewire/flux (FLUXUI_FREE) - v2
- livewire/livewire (LIVEWIRE) - v4
- laravel/boost (BOOST) - v2
- laravel/mcp (MCP) - v0
- laravel/pail (PAIL) - v1
- laravel/pint (PINT) - v1
- laravel/sail (SAIL) - v1
- pestphp/pest (PEST) - v4
- phpunit/phpunit (PHPUNIT) - v12
- tailwindcss (TAILWINDCSS) - v4

## Skills Activation

This project has domain-specific skills available. You MUST activate the relevant skill whenever you work in that domain—don't wait until you're stuck.

- `developing-with-fortify` — Laravel Fortify headless authentication backend development. Activate when implementing authentication features including login, registration, password reset, email verification, two-factor authentication (2FA/TOTP), profile updates, headless auth, authentication scaffolding, or auth guards in Laravel applications.
- `laravel-best-practices` — Apply this skill whenever writing, reviewing, or refactoring Laravel PHP code. This includes creating or modifying controllers, models, migrations, form requests, policies, jobs, scheduled commands, service classes, and Eloquent queries. Triggers for N+1 and query performance issues, caching strategies, authorization and security patterns, validation, error handling, queue and job configuration, route definitions, and architectural decisions. Also use for Laravel code reviews and refactoring existing Laravel code to follow best practices. Covers any task involving Laravel backend PHP code patterns.
- `fluxui-development` — Use this skill for Flux UI development in Livewire applications only. Trigger when working with <flux:*> components, building or customizing Livewire component UIs, creating forms, modals, tables, or other interactive elements. Covers: flux: components (buttons, inputs, modals, forms, tables, date-pickers, kanban, badges, tooltips, etc.), component composition, Tailwind CSS styling, Heroicons/Lucide icon integration, validation patterns, responsive design, and theming. Do not use for non-Livewire frameworks or non-component styling.
- `livewire-development` — Use for any task or question involving Livewire. Activate if user mentions Livewire, wire: directives, or Livewire-specific concepts like wire:model, wire:click, wire:sort, or islands, invoke this skill. Covers building new components, debugging reactivity issues, real-time form validation, drag-and-drop, loading states, migrating from Livewire 3 to 4, converting component formats (SFC/MFC/class-based), and performance optimization. Do not use for non-Livewire reactive UI (React, Vue, Alpine-only, Inertia.js) or standard Laravel forms without Livewire.
- `pest-testing` — Use this skill for Pest PHP testing in Laravel projects only. Trigger whenever any test is being written, edited, fixed, or refactored — including fixing tests that broke after a code change, adding assertions, converting PHPUnit to Pest, adding datasets, and TDD workflows. Always activate when the user asks how to write something in Pest, mentions test files or directories (tests/Feature, tests/Unit, tests/Browser), or needs browser testing, smoke testing multiple pages for JS errors, or architecture tests. Covers: test()/it()/expect() syntax, datasets, mocking, browser testing (visit/click/fill), smoke testing, arch(), Livewire component tests, RefreshDatabase, and all Pest 4 features. Do not use for factories, seeders, migrations, controllers, models, or non-test PHP code.
- `tailwindcss-development` — Always invoke when the user's message includes 'tailwind' in any form. Also invoke for: building responsive grid layouts (multi-column card grids, product grids), flex/grid page structures (dashboards with sidebars, fixed topbars, mobile-toggle navs), styling UI components (cards, tables, navbars, pricing sections, forms, inputs, badges), adding dark mode variants, fixing spacing or typography, and Tailwind v3/v4 work. The core use case: writing or fixing Tailwind utility classes in HTML templates (Blade, JSX, Vue). Skip for backend PHP logic, database queries, API routes, JavaScript with no HTML/CSS component, CSS file audits, build tool configuration, and vanilla CSS.

## Conventions

- You must follow all existing code conventions used in this application. When creating or editing a file, check sibling files for the correct structure, approach, and naming.
- Use descriptive names for variables and methods. For example, `isRegisteredForDiscounts`, not `discount()`.
- Check for existing components to reuse before writing a new one.

## Verification Scripts

- Do not create verification scripts or tinker when tests cover that functionality and prove they work. Unit and feature tests are more important.

## Application Structure & Architecture

- Stick to existing directory structure; don't create new base folders without approval.
- Do not change the application's dependencies without approval.

## Frontend Bundling

- If the user doesn't see a frontend change reflected in the UI, it could mean they need to run `npm run build`, `npm run dev`, or `composer run dev`. Ask them.

## Documentation Files

- You must only create documentation files if explicitly requested by the user.

## Replies

- Be concise in your explanations - focus on what's important rather than explaining obvious details.

=== boost rules ===

# Laravel Boost

## Tools

- Laravel Boost is an MCP server with tools designed specifically for this application. Prefer Boost tools over manual alternatives like shell commands or file reads.
- Use `database-query` to run read-only queries against the database instead of writing raw SQL in tinker.
- Use `database-schema` to inspect table structure before writing migrations or models.
- Use `get-absolute-url` to resolve the correct scheme, domain, and port for project URLs. Always use this before sharing a URL with the user.
- Use `browser-logs` to read browser logs, errors, and exceptions. Only recent logs are useful, ignore old entries.

## Searching Documentation (IMPORTANT)

- Always use `search-docs` before making code changes. Do not skip this step. It returns version-specific docs based on installed packages automatically.
- Pass a `packages` array to scope results when you know which packages are relevant.
- Use multiple broad, topic-based queries: `['rate limiting', 'routing rate limiting', 'routing']`. Expect the most relevant results first.
- Do not add package names to queries because package info is already shared. Use `test resource table`, not `filament 4 test resource table`.

### Search Syntax

1. Use words for auto-stemmed AND logic: `rate limit` matches both "rate" AND "limit".
2. Use `"quoted phrases"` for exact position matching: `"infinite scroll"` requires adjacent words in order.
3. Combine words and phrases for mixed queries: `middleware "rate limit"`.
4. Use multiple queries for OR logic: `queries=["authentication", "middleware"]`.

## Artisan

- Run Artisan commands directly via the command line (e.g., `php artisan route:list`). Use `php artisan list` to discover available commands and `php artisan [command] --help` to check parameters.
- Inspect routes with `php artisan route:list`. Filter with: `--method=GET`, `--name=users`, `--path=api`, `--except-vendor`, `--only-vendor`.
- Read configuration values using dot notation: `php artisan config:show app.name`, `php artisan config:show database.default`. Or read config files directly from the `config/` directory.
- To check environment variables, read the `.env` file directly.

## Tinker

- Execute PHP in app context for debugging and testing code. Do not create models without user approval, prefer tests with factories instead. Prefer existing Artisan commands over custom tinker code.
- Always use single quotes to prevent shell expansion: `php artisan tinker --execute 'Your::code();'`
  - Double quotes for PHP strings inside: `php artisan tinker --execute 'User::where("active", true)->count();'`

=== php rules ===

# PHP

- Always use curly braces for control structures, even for single-line bodies.
- Use PHP 8 constructor property promotion: `public function __construct(public GitHub $github) { }`. Do not leave empty zero-parameter `__construct()` methods unless the constructor is private.
- Use explicit return type declarations and type hints for all method parameters: `function isAccessible(User $user, ?string $path = null): bool`
- Use TitleCase for Enum keys: `FavoritePerson`, `BestLake`, `Monthly`.
- Prefer PHPDoc blocks over inline comments. Only add inline comments for exceptionally complex logic.
- Use array shape type definitions in PHPDoc blocks.

=== deployments rules ===

# Deployment

- Laravel can be deployed using [Laravel Cloud](https://cloud.laravel.com/), which is the fastest way to deploy and scale production Laravel applications.

=== herd rules ===

# Laravel Herd

- The application is served by Laravel Herd at `https?://[kebab-case-project-dir].test`. Use the `get-absolute-url` tool to generate valid URLs. Never run commands to serve the site. It is always available.
- Use the `herd` CLI to manage services, PHP versions, and sites (e.g. `herd sites`, `herd services:start <service>`, `herd php:list`). Run `herd list` to discover all available commands.

=== tests rules ===

# Test Enforcement

- Every change must be programmatically tested. Write a new test or update an existing test, then run the affected tests to make sure they pass.
- Run the minimum number of tests needed to ensure code quality and speed. Use `php artisan test --compact` with a specific filename or filter.

=== fortify/core rules ===

# Laravel Fortify

- Fortify is a headless authentication backend that provides authentication routes and controllers for Laravel applications.
- IMPORTANT: Always use the `search-docs` tool for detailed Laravel Fortify patterns and documentation.
- IMPORTANT: Activate `developing-with-fortify` skill when working with Fortify authentication features.

=== laravel/core rules ===

# Do Things the Laravel Way

- Use `php artisan make:` commands to create new files (i.e. migrations, controllers, models, etc.). You can list available Artisan commands using `php artisan list` and check their parameters with `php artisan [command] --help`.
- If you're creating a generic PHP class, use `php artisan make:class`.
- Pass `--no-interaction` to all Artisan commands to ensure they work without user input. You should also pass the correct `--options` to ensure correct behavior.

### Model Creation

- When creating new models, create useful factories and seeders for them too. Ask the user if they need any other things, using `php artisan make:model --help` to check the available options.

## APIs & Eloquent Resources

- For APIs, default to using Eloquent API Resources and API versioning unless existing API routes do not, then you should follow existing application convention.

## URL Generation

- When generating links to other pages, prefer named routes and the `route()` function.

## Testing

- When creating models for tests, use the factories for the models. Check if the factory has custom states that can be used before manually setting up the model.
- Faker: Use methods such as `$this->faker->word()` or `fake()->randomDigit()`. Follow existing conventions whether to use `$this->faker` or `fake()`.
- When creating tests, make use of `php artisan make:test [options] {name}` to create a feature test, and pass `--unit` to create a unit test. Most tests should be feature tests.

## Vite Error

- If you receive an "Illuminate\Foundation\ViteException: Unable to locate file in Vite manifest" error, you can run `npm run build` or ask the user to run `npm run dev` or `composer run dev`.

=== laravel/v12 rules ===

# Laravel 12

- CRITICAL: ALWAYS use `search-docs` tool for version-specific Laravel documentation and updated code examples.
- Since Laravel 11, Laravel has a new streamlined file structure which this project uses.

## Laravel 12 Structure

- In Laravel 12, middleware are no longer registered in `app/Http/Kernel.php`.
- Middleware are configured declaratively in `bootstrap/app.php` using `Application::configure()->withMiddleware()`.
- `bootstrap/app.php` is the file to register middleware, exceptions, and routing files.
- `bootstrap/providers.php` contains application specific service providers.
- The `app/Console/Kernel.php` file no longer exists; use `bootstrap/app.php` or `routes/console.php` for console configuration.
- Console commands in `app/Console/Commands/` are automatically available and do not require manual registration.

## Database

- When modifying a column, the migration must include all of the attributes that were previously defined on the column. Otherwise, they will be dropped and lost.
- Laravel 12 allows limiting eagerly loaded records natively, without external packages: `$query->latest()->limit(10);`.

### Models

- Casts can and likely should be set in a `casts()` method on a model rather than the `$casts` property. Follow existing conventions from other models.

=== livewire/core rules ===

# Livewire

- Livewire allow to build dynamic, reactive interfaces in PHP without writing JavaScript.
- You can use Alpine.js for client-side interactions instead of JavaScript frameworks.
- Keep state server-side so the UI reflects it. Validate and authorize in actions as you would in HTTP requests.

=== pint/core rules ===

# Laravel Pint Code Formatter

- If you have modified any PHP files, you must run `vendor/bin/pint --dirty --format agent` before finalizing changes to ensure your code matches the project's expected style.
- Do not run `vendor/bin/pint --test --format agent`, simply run `vendor/bin/pint --format agent` to fix any formatting issues.

=== pest/core rules ===

## Pest

- This project uses Pest for testing. Create tests: `php artisan make:test --pest {name}`.
- Run tests: `php artisan test --compact` or filter: `php artisan test --compact --filter=testName`.
- Do NOT delete tests without approval.

</laravel-boost-guidelines>

</INSTRUCTIONS>

<environment_context>
  <cwd>/Users/fabianwesner/Herd/shop</cwd>
  <shell>zsh</shell>
  <current_date>2026-05-03</current_date>
  <timezone>Europe/Berlin</timezone>
  <subagents>
    - 019df0be-48af-7a51-8af7-26927c73f182: Mill
    - 019df0be-4901-7860-b0a7-7b1ba065d58c: Ohm
  </subagents>
</environment_context>