php artisan test --compact tests/Browser/Storefront/ResponsiveTest.php

  ⨯⨯⨯⨯⨯..⨯
  ────────────────────────────────────────────────────────────────────────────
   FAILED  Tests\Browser\Storefront\ResponsiveTest > mobile home shows hambu…
  Expected no JavaScript errors on the page initially with the url [http://127.0.0.1:60518/], but found 10: Uncaught TypeError: Cannot read properties of null (reading 'addEventListener'), Uncaught TypeError: Cannot read properties of null (reading 'addEventListener'), Uncaught TypeError: Cannot read properties of null (reading 'addEventListener'), Uncaught TypeError: Cannot read properties of null (reading 'addEventListener'), Uncaught TypeError: Cannot read properties of null (reading 'addEventListener'), Uncaught TypeError: Cannot read properties of null (reading 'addEventListener'), Uncaught TypeError: Cannot read properties of null (reading 'addEventListener'), Uncaught TypeError: Cannot read properties of null (reading 'addEventListener'), Uncaught TypeError: Cannot read properties of null (reading 'addEventListener'), Uncaught TypeError: Cannot read properties of null (reading 'addEventListener')
Failed asserting that an array is empty. A screenshot of the page has been saved to [Tests/Browser/Screenshots/mobile_home_shows_hamburger_navigation_without_horizontal_overflow].

The following JavaScript errors were found:
- Uncaught TypeError: Cannot read properties of null (reading 'addEventListener')
- Uncaught TypeError: Cannot read properties of null (reading 'addEventListener')
- Uncaught TypeError: Cannot read properties of null (reading 'addEventListener')
- Uncaught TypeError: Cannot read properties of null (reading 'addEventListener')
- Uncaught TypeError: Cannot read properties of null (reading 'addEventListener')
- Uncaught TypeError: Cannot read properties of null (reading 'addEventListener')
- Uncaught TypeError: Cannot read properties of null (reading 'addEventListener')
- Uncaught TypeError: Cannot read properties of null (reading 'addEventListener')
- Uncaught TypeError: Cannot read properties of null (reading 'addEventListener')
- Uncaught TypeError: Cannot read properties of null (reading 'addEventListener')

  at tests/Browser/Storefront/ResponsiveTest.php:97
     93▕         ->click('@mobile-menu-button')
     94▕         ->wait(1)
     95▕         ->assertVisible('nav[aria-label="Mobile navigation"]')
     96▕         ->assertSee('Collections')
  ➜  97▕         ->assertNoJavaScriptErrors();
     98▕ });
     99▕
    100▕ test('mobile product detail is stacked and keeps purchase controls usable', function (): void {
    101▕     $page = storefrontResponsiveMobileVisit('/products/classic-cotton-t-shirt')

  ────────────────────────────────────────────────────────────────────────────
   FAILED  Tests\Browser\Storefront\ResponsiveTest > mobile product detail i…
  Expected no JavaScript errors on the page initially with the url [http://127.0.0.1:60518/products/classic-cotton-t-shirt], but found 10: Uncaught TypeError: Cannot read properties of null (reading 'addEventListener'), Uncaught TypeError: Cannot read properties of null (reading 'addEventListener'), Uncaught TypeError: Cannot read properties of null (reading 'addEventListener'), Uncaught TypeError: Cannot read properties of null (reading 'addEventListener'), Uncaught TypeError: Cannot read properties of null (reading 'addEventListener'), Uncaught TypeError: Cannot read properties of null (reading 'addEventListener'), Uncaught TypeError: Cannot read properties of null (reading 'addEventListener'), Uncaught TypeError: Cannot read properties of null (reading 'addEventListener'), Uncaught TypeError: Cannot read properties of null (reading 'addEventListener'), Uncaught TypeError: Cannot read properties of null (reading 'addEventListener')
Failed asserting that an array is empty. A screenshot of the page has been saved to [Tests/Browser/Screenshots/mobile_product_detail_is_stacked_and_keeps_purchase_controls_usable].

The following JavaScript errors were found:
- Uncaught TypeError: Cannot read properties of null (reading 'addEventListener')
- Uncaught TypeError: Cannot read properties of null (reading 'addEventListener')
- Uncaught TypeError: Cannot read properties of null (reading 'addEventListener')
- Uncaught TypeError: Cannot read properties of null (reading 'addEventListener')
- Uncaught TypeError: Cannot read properties of null (reading 'addEventListener')
- Uncaught TypeError: Cannot read properties of null (reading 'addEventListener')
- Uncaught TypeError: Cannot read properties of null (reading 'addEventListener')
- Uncaught TypeError: Cannot read properties of null (reading 'addEventListener')
- Uncaught TypeError: C…7513 chars truncated…t TypeError: Cannot read properties of null (reading 'addEventListener')
Failed asserting that an array is empty. A screenshot of the page has been saved to [Tests/Browser/Screenshots/mobile_checkout_reaches_shipping_methods_after_address_entry].

The following JavaScript errors were found:
- Uncaught TypeError: Cannot read properties of null (reading 'addEventListener')
- Uncaught TypeError: Cannot read properties of null (reading 'addEventListener')
- Uncaught TypeError: Cannot read properties of null (reading 'addEventListener')
- Uncaught TypeError: Cannot read properties of null (reading 'addEventListener')
- Uncaught TypeError: Cannot read properties of null (reading 'addEventListener')
- Uncaught TypeError: Cannot read properties of null (reading 'addEventListener')
- Uncaught TypeError: Cannot read properties of null (reading 'addEventListener')
- Uncaught TypeError: Cannot read properties of null (reading 'addEventListener')
- Uncaught TypeError: Cannot read properties of null (reading 'addEventListener')
- Uncaught TypeError: Cannot read properties of null (reading 'addEventListener')
- Uncaught TypeError: Cannot read properties of null (reading 'addEventListener')
- Uncaught TypeError: Cannot read properties of null (reading 'addEventListener')
- Uncaught TypeError: Cannot read properties of null (reading 'addEventListener')
- Uncaught TypeError: Cannot read properties of null (reading 'addEventListener')
- Uncaught TypeError: Cannot read properties of null (reading 'addEventListener')
- Uncaught TypeError: Cannot read properties of null (reading 'addEventListener')
- Uncaught TypeError: Cannot read properties of null (reading 'addEventListener')
- Uncaught TypeError: Cannot read properties of null (reading 'addEventListener')
- Uncaught TypeError: Cannot read properties of null (reading 'addEventListener')
- Uncaught TypeError: Cannot read properties of null (reading 'addEventListener')

  at tests/Browser/Storefront/ResponsiveTest.php:154
    150▕     storefrontResponsiveFillAddress($page)
    151▕         ->click('form[wire\\:submit="saveAddress"] button[type="submit"]')
    152▕         ->wait(1)
    153▕         ->assertSee('Standard Shipping')
  ➜ 154▕         ->assertNoJavaScriptErrors();
    155▕
    156▕     storefrontResponsiveAssertNoHorizontalScroll($page);
    157▕ });
    158▕

  ────────────────────────────────────────────────────────────────────────────
   FAILED  Tests\Browser\Storefront\ResponsiveTest > mobile collection keeps…
  Expected no JavaScript errors on the page initially with the url [http://127.0.0.1:60518/collections/t-shirts], but found 10: Uncaught TypeError: Cannot read properties of null (reading 'addEventListener'), Uncaught TypeError: Cannot read properties of null (reading 'addEventListener'), Uncaught TypeError: Cannot read properties of null (reading 'addEventListener'), Uncaught TypeError: Cannot read properties of null (reading 'addEventListener'), Uncaught TypeError: Cannot read properties of null (reading 'addEventListener'), Uncaught TypeError: Cannot read properties of null (reading 'addEventListener'), Uncaught TypeError: Cannot read properties of null (reading 'addEventListener'), Uncaught TypeError: Cannot read properties of null (reading 'addEventListener'), Uncaught TypeError: Cannot read properties of null (reading 'addEventListener'), Uncaught TypeError: Cannot read properties of null (reading 'addEventListener')
Failed asserting that an array is empty. A screenshot of the page has been saved to [Tests/Browser/Screenshots/mobile_collection_keeps_filters_accessible].

The following JavaScript errors were found:
- Uncaught TypeError: Cannot read properties of null (reading 'addEventListener')
- Uncaught TypeError: Cannot read properties of null (reading 'addEventListener')
- Uncaught TypeError: Cannot read properties of null (reading 'addEventListener')
- Uncaught TypeError: Cannot read properties of null (reading 'addEventListener')
- Uncaught TypeError: Cannot read properties of null (reading 'addEventListener')
- Uncaught TypeError: Cannot read properties of null (reading 'addEventListener')
- Uncaught TypeError: Cannot read properties of null (reading 'addEventListener')
- Uncaught TypeError: Cannot read properties of null (reading 'addEventListener')
- Uncaught TypeError: Cannot read properties of null (reading 'addEventListener')
- Uncaught TypeError: Cannot read properties of null (reading 'addEventListener')

  at tests/Browser/Storefront/ResponsiveTest.php:192
    188▕         ->wait(1)
    189▕         ->assertSee('No products found');
    190▕
    191▕     storefrontResponsiveAssertNoHorizontalScroll($page)
  ➜ 192▕         ->assertNoJavaScriptErrors();
    193▕ });
    194▕

  Tests:    6 failed, 2 passed (45 assertions)
  Duration: 68.79s


The first run found a real JS regression from the new mobile flyout: Flux’s close wrapper is throwing when used around plain anchors here. I’m changing the menu links to navigate normally and letting page navigation close the flyout instead of wrapping every link in `flux:modal.close`.


rg -n "addEventListener|ui-close|data-flux-modal-close|modal-close" vendor/livewire/flux public/build resources/js -g '*.js' -g '*.blade.php'


sed -n '1,220p' resources/views/layouts/storefront.blade.php


git diff -- resources/views/layouts/storefront.blade.php tests/Browser/Storefront/ResponsiveTest.php


sed -n '1,220p' resources/views/layouts/storefront.blade.php

<!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>
                    <flux:modal.trigger name="mobile-navigation">
                        <flux:button variant="subtle" icon="bars-3" aria-label="Open menu" class="md:hidden" data-test="mobile-menu-button" />
                    </flux:modal.trigger>

        <flux:modal name="mobile-navigation" flyout position="left" class="w-80 max-w-[calc(100vw-2rem)] md:hidden">

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

                    @foreach ($mainLinks as $item)
                        <flux:modal.close>

                                {{ $item['label'] }}

                        </flux:modal.close>

                        @foreach (($item['children'] ?? []) as $child)
                            <flux:modal.close>

                                    {{ $child['label'] }}

                            </flux:modal.close>
                        @endforeach
                    @endforeach

                        <flux:modal.close>

                                Search

                        </flux:modal.close>

                        <flux:modal.close>

                                Account

                        </flux:modal.close>

        </flux:modal>

        <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


git diff -- resources/views/layouts/storefront.blade.php tests/Browser/Storefront/ResponsiveTest.php

diff --git a/resources/views/layouts/storefront.blade.php b/resources/views/layouts/storefront.blade.php
index 4f9c5c0a..3e9d2f14 100644
--- a/resources/views/layouts/storefront.blade.php
+++ b/resources/views/layouts/storefront.blade.php
@@ -65,10 +65,55 @@
                     <flux:modal.trigger name="cart-drawer">
                         <flux:button variant="subtle" icon="shopping-bag" aria-label="Cart" />
                     </flux:modal.trigger>
+                    <flux:modal.trigger name="mobile-navigation">
+                        <flux:button variant="subtle" icon="bars-3" aria-label="Open menu" class="md:hidden" data-test="mobile-menu-button" />
+                    </flux:modal.trigger>

+        <flux:modal name="mobile-navigation" flyout position="left" class="w-80 max-w-[calc(100vw-2rem)] md:hidden">
+

+

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

+

+
+
+                    @foreach ($mainLinks as $item)
+                        <flux:modal.close>
+
+                                {{ $item['label'] }}
+
+                        </flux:modal.close>
+
+                        @foreach (($item['children'] ?? []) as $child)
+                            <flux:modal.close>
+
+                                    {{ $child['label'] }}
+
+                            </flux:modal.close>
+                        @endforeach
+                    @endforeach
+
+
+

+

+                        <flux:modal.close>
+
+                                Search
+
+                        </flux:modal.close>
+
+                        <flux:modal.close>
+
+                                Account
+
+                        </flux:modal.close>
+

+

+

+        </flux:modal>
+
         <livewire:storefront.cart-drawer />


rg -n "addEventListener|ui-close|data-flux-modal-close|modal-close" vendor/livewire/flux public/build resources/js -g '*.js' -g '*.blade.php'

rg -n 'addEventListener|ui-close|data-flux-modal-close|modal-close' vendor/livewire/flux public/build resources/js -g '*.js' -g '*.blade.php'

Question: addEventListener|ui-close|data-flux-modal-close|modal-close


Output:
Chunk ID: c15b1a
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 48230
Output:
Total output lines: 8

vendor/livewire/flux/dist/flux-lite.min.js:63:`}var Ce=null;function Bt(t){let e=so();if(Ce===null)try{Ce=new CSSStyleSheet,Ce.replaceSync(e)}catch{Ce=!1}if(Ce===!1){let i=document.createElement("style");i.textContent=e,t instanceof Document?t.head.prepend(i):t.prepend(i)}else t.adoptedStyleSheets=[Ce,...t.adoptedStyleSheets]}function oo(){if(typeof window>"u")return;window.ToggleEvent=window.ToggleEvent||ht;function t(l){return l?.includes(":popover-open")&&(l=l.replace(to,"$1.\\:popover-open")),l}be(Document.prototype,"querySelector",t),be(Document.prototype,"querySelectorAll",t),be(Element.prototype,"querySelector",t),be(Element.prototype,"querySelectorAll",t),be(Element.prototype,"matches",t),be(Element.prototype,"closest",t),be(DocumentFragment.prototype,"querySelectorAll",t),Object.defineProperties(HTMLElement.prototype,{popover:{enumerable:!0,configurable:!0,get(){if(!this.hasAttribute("popover"))return null;let l=(this.getAttribute("popover")||"").toLowerCase();return l===""||l=="auto"?"auto":"manual"},set(l){l===null?this.removeAttribute("popover"):this.setAttribute("popover",l)}},showPopover:{enumerable:!0,configurable:!0,value(){qt(this)}},hidePopover:{enumerable:!0,configurable:!0,value(){$e(this,!0,!0)}},togglePopover:{enumerable:!0,configurable:!0,value(l){ze.get(this)==="showing"&&l===void 0||l===!1?$e(this,!0,!0):(l===void 0||l===!0)&&qt(this)}}});let e=Element.prototype.attachShadow;e&&Object.defineProperties(Element.prototype,{attachShadow:{enumerable:!0,configurable:!0,writable:!0,value(l){let a=e.call(this,l);return Bt(a),a}}});let i=HTMLElement.prototype.attachInternals;i&&Object.defineProperties(HTMLElement.prototype,{attachInternals:{enumerable:!0,configurable:!0,writable:!0,value(){let l=i.call(this);return l.shadowRoot&&Bt(l.shadowRoot),l}}});let s=new WeakMap;function o(l){Object.defineProperties(l.prototype,{popoverTargetElement:{enumerable:!0,configurable:!0,set(a){if(a===null)this.removeAttribute("popovertarget"),s.delete(this);else if(a instanceof Element)this.setAttribute("popovertarget",""),s.set(this,a);else throw new TypeError("popoverTargetElement must be an element or null")},get(){if(this.localName!=="button"&&this.localName!=="input"||this.localName==="input"&&this.type!=="reset"&&this.type!=="image"&&this.type!=="button"||this.disabled||this.form&&this.type==="submit")return null;let a=s.get(this);if(a&&a.isConnected)return a;if(a&&!a.isConnected)return s.delete(this),null;let f=Te(this),d=this.getAttribute("popovertarget");return(f instanceof Document||f instanceof Ki)&&d&&f.getElementById(d)||null}},popoverTargetAction:{enumerable:!0,configurable:!0,get(){let a=(this.getAttribute("popovertargetaction")||"").toLowerCase();return a==="show"||a==="hide"?a:"toggle"},set(a){this.setAttribute("popovertargetaction",a)}}})}o(HTMLButtonElement),o(HTMLInputElement);let n=l=>{let a=l.composedPath(),f=a[0];if(!(f instanceof Element)||f?.shadowRoot)return;let d=Te(f);if(!(d instanceof Ki||d instanceof Document))return;let m=a.find(g=>g.matches?.("[popovertargetaction],[popovertarget]"));if(m){Us(m),l.preventDefault();return}},r=l=>{let a=l.key,f=l.target;!l.defaultPrevented&&f&&(a==="Escape"||a==="Esc")&&dt(f.ownerDocument,!0,!0)};(l=>{l.addEventListener("click",n),l.addEventListener("keydown",r),l.addEventListener("pointerdown",ji),l.addEventListener("pointerup",ji)})(document),Bt(document)}eo()||oo();var vt=class extends Event{oldState;newState;constructor(t,{oldState:e="",newState:i="",...s}={}){super(t,s),this.oldState=String(e||""),this.newState=String(i||"")}},Xi=new WeakMap;function ts(t,e,i){Xi.set(t,setTimeout(()=>{Xi.has(t)&&t.dispatchEvent(new vt("toggle",{cancelable:!1,oldState:e,newState:i}))},0))}var Gt=globalThis.ShadowRoot||function(){},no=globalThis.HTMLDialogElement||function(){},ft=new WeakMap,ie=new WeakMap,Ke=new WeakMap;function pt(t){return Ke.get(t)||"hidden"}var bt=new WeakMap;function ro(t){let e=t.popoverTargetElement;if(!(e instanceof HTMLElement))return;let i=pt(e);t.popoverTargetAction==="show"&&i==="showing"||t.popoverTargetAction==="hide"&&i==="hidden"||(i==="showing"?je(e,!0,!0):ve(e,!1)&&(bt.set(e,t),Ut(e)))}function ve(t,e){return!(t.popover!=="auto"&&t.popover!=="manual"||!t.isConnected||e&&pt(t)!=="showing"||!e&&pt(t)!=="hidden"||t instanceof no&&t.hasAttribute("open")||document.fullscreenElement===t)}function Ji(t){return t?Array.from(ie.get(t.ownerDocument)||[]).indexOf(t)+1:0}function lo(t){let e=is(t),i=ao(t);return Ji(e)>Ji(i)?e:i}function mt(t){let e=ie.get(t);for(let i of e||[])if(!i.isConnected)e.delete(i);else return i;return null}function Pe(t){return typeof t.getRootNode=="function"?t.getRootNode():t.parentNode?Pe(t.parentNode):t}function is(t){for(;t;){if(t instanceof HTMLElement&&t.popover==="auto"&&Ke.get(t)==="showing")return t;if(t=t instanceof Element&&t.assignedSlot||t.parentElement||Pe(t),t instanceof Gt&&(t=t.host),t instanceof Document)return}}function ao(t){for(;t;){let e=t.popoverTargetElement;if(e instanceof HTMLElement)return e;if(t=t.parentElement||Pe(t),t instanceof Gt&&(t=t.host),t instanceof Document)return}}function uo(t){let e=new Map,i=0;for(let n of ie.get(t.ownerDocument)||[])e.set(n,i),i+=1;e.set(t,i),i+=1;let s=null;function o(n){let r=is(n);if(r===null)return null;let u=e.get(r);(s===null||e.get(s)<u)&&(s=r)}return o(t.parentElement||Pe(t)),s}function co(t){return t.hidden||t instanceof Gt||(t instanceof HTMLButtonElement||t instanceof HTMLInputElement||t instanceof HTMLSelectElement||t instanceof HTMLTextAreaElement||t instanceof HTMLOptGroupElement||t instanceof HTMLOptionElement||t instanceof HTMLFieldSetElement)&&t.disabled||t instanceof HTMLInputElement&&t.type==="hidden"||t instanceof HTMLAnchorElement&&t.href===""?!1:typeof t.tabIndex=="number"&&t.tabIndex!==-1}function ho(t){if(t.shadowRoot&&t.shadowRoot.delegatesFocus!==!0)return null;let e=t;e.shadowRoot&&(e=e.shadowRoot);let i=e.querySelector("[autofocus]");if(i)return i;{let n=e.querySelectorAll("slot");for(let r of n){let u=r.assignedElements({flatten:!0});for(let l of u){if(l.hasAttribute("autofocus"))return l;if(i=l.querySelector("[autofocus]"),i)return i}}}let s=t.ownerDocument.createTreeWalker(e,NodeFilter.SHOW_ELEMENT),o=s.currentNode;for(;o;){if(co(o))return o;o=s.nextNode()}}function fo(t){ho(t)?.focus()}var gt=new WeakMap;function Ut(t){if(!ve(t,!1))return;let e=t.ownerDocument;if(!t.dispatchEvent(new vt("beforetoggle",{cancelable:!0,oldState:"closed",newState:"open"}))||!ve(t,!1))return;let i=!1;if(t.popover==="auto"){let o=t.getAttribute("popover"),n=uo(t)||e;if(wt(n,!1,!0),o!==t.getAttribute("popover")||!ve(t,!1))return}mt(e)||(i=!0),gt.delete(t);let s=e.activeElement;t.classList.add(":popover-open"),Ke.set(t,"showing"),ft.has(e)||ft.set(e,new Set),ft.get(e).add(t),fo(t),t.popover==="auto"&&(ie.has(e)||ie.set(e,new Set),ie.get(e).add(t),ss(bt.get(t),!0)),i&&s&&t.popover==="auto"&&gt.set(t,s),ts(t,"closed","open")}function je(t,e=!1,i=!1){if(!ve(t,!0))return;let s=t.ownerDocument;if(t.popover==="auto"&&(wt(t,e,i),!ve(t,!0))||(ss(bt.get(t),!1),bt.delete(t),i&&(t.dispatchEvent(new vt("beforetoggle",{oldState:"open",newState:"closed"})),!ve(t,!0))))return;ft.get(s)?.delete(t),ie.get(s)?.delete(t),t.classList.remove(":popover-open"),Ke.set(t,"hidden"),i&&ts(t,"open","closed");let o=gt.get(t);o&&(gt.delete(t),e&&o.focus())}function Qi(t,e=!1,i=!1){let s=mt(t);for(;s;)je(s,e,i),s=mt(t)}function wt(t,e,i){let s=t.ownerDocument||t;if(t instanceof Document)return Qi(s,e,i);let o=null,n=!1;for(let r of ie.get(s)||[])if(r===t)n=!0;else if(n){o=r;break}if(!n)return Qi(s,e,i);for(;o&&pt(o)==="showing"&&ie.get(s)?.size;)je(o,e,i)}var zt=new WeakMap;function Zi(t){if(!t.isTrusted)return;let e=t.composedPath()[0];if(!e)return;let i=e.ownerDocument;if(!mt(i))return;let o=lo(e);if(o&&t.type==="pointerdown")zt.set(i,o);else if(t.type==="pointerup"){let n=zt.get(i)===o;zt.delete(i),n&&wt(o||i,!1,!0)}}var jt=new WeakMap;function ss(t,e=!1){if(!t)return;jt.has(t)||jt.set(t,t.getAttribute("aria-expanded"));let i=t.popoverTargetElement;if(i instanceof HTMLElement&&i.popover==="auto")t.setAttribute("aria-expanded",String(e));else{let s=jt.get(t);s?t.setAttribute("aria-expanded",s):t.removeAttribute("aria-expanded")}}var es=globalThis.ShadowRoot||function(){};function os(){return typeof HTMLElement<"u"&&typeof HTMLElement.prototype=="object"&&"popover"in HTMLElement.prototype}function ns(){return!!(document.body?.showPopover&&!/native code/i.test(document.body.showPopover.toString()))}function ge(t,e,i){let s=t[e];Object.defineProperty(t,e,{value(o){return s.call(this,i(o))}})}var po=/(^|[^\\]):popover-open\b/g;function bo(){return typeof globalThis.CSSLayerBlockRule=="function"}function mo(){let t=bo();return`
vendor/livewire/flux/dist/flux-lite.min.js:125:`}var Oe=null;function Kt(t){let e=mo();if(Oe===null)try{Oe=new CSSStyleSheet,Oe.replaceSync(e)}catch{Oe=!1}if(Oe===!1){let i=document.createElement("style");i.textContent=e,t instanceof Document?t.head.prepend(i):t.prepend(i)}else t.adoptedStyleSheets=[Oe,...t.adoptedStyleSheets]}function rs(){if(typeof window>"u")return;window.ToggleEvent=window.ToggleEvent||vt;function t(l){return l?.includes(":popover-open")&&(l=l.replace(po,"$1.\\:popover-open")),l}ge(Document.prototype,"querySelector",t),ge(Document.prototype,"querySelectorAll",t),ge(Element.prototype,"querySelector",t),ge(Element.prototype,"querySelectorAll",t),ge(Element.prototype,"matches",t),ge(Element.prototype,"closest",t),ge(DocumentFragment.prototype,"querySelectorAll",t),Object.defineProperties(HTMLElement.prototype,{popover:{enumerable:!0,configurable:!0,get(){if(!this.hasAttribute("popover"))return null;let l=(this.getAttribute("popover")||"").toLowerCase();return l===""||l=="auto"?"auto":"manual"},set(l){l===null?this.removeAttribute("popover"):this.setAttribute("popover",l)}},showPopover:{enumerable:!0,configurable:!0,value(){Ut(this)}},hidePopover:{enumerable:!0,configurable:!0,value(){je(this,!0,!0)}},togglePopover:{enumerable:!0,configurable:!0,value(l){Ke.get(this)==="showing"&&l===void 0||l===!1?je(this,!0,!0):(l===void 0||l===!0)&&Ut(this)}}});let e=Element.prototype.attachShadow;e&&Object.defineProperties(Element.prototype,{attachShadow:{enumerable:!0,configurable:!0,writable:!0,value(l){let a=e.call(this,l);return Kt(a),a}}});let i=HTMLElement.prototype.attachInternals;i&&Object.defineProperties(HTMLElement.prototype,{attachInternals:{enumerable:!0,configurable:!0,writable:!0,value(){let l=i.call(this);return l.shadowRoot&&Kt(l.shadowRoot),l}}});let s=new WeakMap;function o(l){Object.defineProperties(l.prototype,{popoverTargetElement:{enumerable:!0,configurable:!0,set(a){if(a===null)this.removeAttribute("popovertarget"),s.delete(this);else if(a instanceof Element)this.setAttribute("popovertarget",""),s.set(this,a);else throw new TypeError("popoverTargetElement must be an element or null")},get(){if(this.localName!=="button"&&this.localName!=="input"||this.localName==="input"&&this.type!=="reset"&&this.type!=="image"&&this.type!=="button"||this.disabled||this.form&&this.type==="submit")return null;let a=s.get(this);if(a&&a.isConnected)return a;if(a&&!a.isConnected)return s.delete(this),null;let f=Pe(this),d=this.getAttribute("popovertarget");return(f instanceof Document||f instanceof es)&&d&&f.getElementById(d)||null}},popoverTargetAction:{enumerable:!0,configurable:!0,get(){let a=(this.getAttribute("popovertargetaction")||"").toLowerCase();return a==="show"||a==="hide"?a:"toggle"},set(a){this.setAttribute("popovertargetaction",a)}}})}o(HTMLButtonElement),o(HTMLInputElement);let n=l=>{let a=l.composedPath(),f=a[0];if(!(f instanceof Element)||f?.shadowRoot)return;let d=Pe(f);if(!(d instanceof es||d instanceof Document))return;let m=a.find(g=>g.matches?.("[popovertargetaction],[popovertarget]"));if(m){ro(m),l.preventDefault();return}},r=l=>{let a=l.key,f=l.target;!l.defaultPrevented&&f&&(a==="Escape"||a==="Esc")&&wt(f.ownerDocument,!0,!0)};(l=>{l.addEventListener("click",n),l.addEventListener("keydown",r),l.addEventListener("pointerdown",Zi),l.addEventListener("pointerup",Zi)})(document),Kt(document)}var yt;function T(t){let e=t({css:(i,...s)=>`@layer base { ${i.raw[0]+s.join("")} }`});if(document.adoptedStyleSheets===void 0){let i=document.createElement("style");i.textContent=e,document.head.appendChild(i);return}yt===void 0&&(yt=new CSSStyleSheet,document.adoptedStyleSheets=[...document.adoptedStyleSheets,yt]),yt.insertRule(e)}function xt(t,e){let i=t;for(;i;){if(e(i))return i;i=i.parentElement}}function I(t,e){let i=document.createTreeWalker(t,NodeFilter.SHOW_ELEMENT,e?{acceptNode:s=>{let o,n;return e(s,{skip:()=>o=!0,reject:()=>n=!0}),o?NodeFilter.FILTER_SKIP:n?NodeFilter.FILTER_REJECT:NodeFilter.FILTER_ACCEPT}}:{});return new Yt(i)}var Yt=class{constructor(e){this.walker=e}from(e){return this.walker.currentNode=e,this}first(){return this.walker.firstChild()}last(){return this.walker.lastChild()}next(e){return this.walker.currentNode=e,this.walker.nextSibling()}nextOrFirst(e){let i=this.next(e);return i||(this.walker.currentNode=this.walker.root,this.first())}prev(e){return this.walker.currentNode=e,this.walker.previousSibling()}prevOrLast(e){let i=this.prev(e);return i||(this.walker.currentNode=this.walker.root,this.last())}closest(e,i){let s=this.from(e).walker;for(;s.currentNode;){if(i(s.currentNode))return s.currentNode;s.parentNode()}}contains(e){return this.find(i=>i===e)}find(e){return this.walk((i,s)=>{e(i)&&s(i)})}findOrFirst(e){return this.find(e)||(this.walker.currentNode=this.walker.root),this.first()}each(e){this.walk(i=>e(i))}some(e){return!!this.find(e)}every(e){let i=!0;return this.walk(s=>{e(s)||(i=!1)}),i}map(e){let i=[];return this.walk(s=>i.push(e(s))),i}filter(e){let i=[];return this.walk(s=>e(s)&&i.push(s)),i}walk(e){let i,s=this.walker,o;for(;s.nextNode()&&(i=s.currentNode,e(i,n=>o=n),o===void 0););return o}};function A(t,e){customElements.define(`ui-${t}`,e)}function p(t,e,i,s={}){return t.addEventListener(e,i,s),{off:()=>t.removeEventListener(e,i),pause:o=>{t.removeEventListener(e,i),o(),t.addEventListener(e,i)}}}function as(t){return["a[href]","area[href]","input:not([disabled])","select:not([disabled])","textarea:not([disabled])","button:not([disabled])","iframe","object","embed","[tabindex]","[contenteditable]"].some(i=>t.matches(i))&&t.tabIndex>=0}function go(t,e){let i;return function(){let s=this,o=arguments;i||(t.apply(s,o),i=!0,setTimeout(()=>i=!1,e))}}var Ue="pointer";document.addEventListener("keydown",()=>Ue="keyboard",{capture:!0});document.addEventListener("pointerdown",t=>{Ue=t.pointerType==="mouse"?"mouse":"touch"},{capture:!0});document.addEventListener("pointermove",t=>{Ue=t.pointerType==="mouse"?"mouse":"touch"},{capture:!0});function we(){return Ue==="keyboard"}function Et(){return Ue==="touch"}function _t(t,e){let i="",s=So(()=>{i=""},300);t.addEventListener("keydown",o=>{o.key.length===1&&/[a-zA-Z]/.test(o.key)&&(i+=o.key,e(i),o.stopPropagation()),s()})}function vo(t,e){return"lofi-"+(e?e+"-":"")+Math.random().toString(16).slice(2)}function N(t,e){let i=t.hasAttribute("id")?t.getAttribute("id"):vo(t,e);return c(t,"id",i),t._x_bindings||(t._x_bindings={}),t._x_bindings.id||(t._x_bindings.id=i),i}function D(){let t=!1;return e=>(...i)=>{t||(t=!0,e(...i),t=!1)}}function Me(t,e,{gain:i,lose:s,focusable:o,useSafeArea:n}){let r=!1,u=v=>{we()&&(t.contains(v.target)||e.contains(v.target)?(r=!0,i()):(r=!1,s()))};o&&document.addEventListener("focusin",u);let l=()=>{document.removeEventListener("focusin",u)},a=()=>{},f=()=>{},d=()=>{r=!1,s(),a(),f()},m=()=>{r=!1,a(),f()},g=v=>{Et()||r||(r=!0,i(),setTimeout(()=>{let{safeArea:y,redraw:E,remove:_}=n?wo(t,e,v.clientX,v.clientY):_o();a=_;let L,P=go(R=>{let z=e.getBoundingClientRect(),ee=t.getBoundingClientRect(),q;switch(y.contains(R.target)&&yo(ee,z,R.clientX,R.clientY)?q="safeArea":e.contains(R.target)?q="panel":t.contains(R.target)?q="trigger":q="outside",L&&clearTimeout(L),q){case"outside":d();break;case"trigger":E(R.clientX,R.clientY);break;case"panel":a();break;case"safeArea":E(R.clientX,R.clientY),L=setTimeout(()=>{d()},300);break;default:break}},100);document.addEventListener("pointermove",P),f=()=>document.removeEventListener("pointermove",P)}))};t.addEventListener("pointerenter",g);let h=()=>{t.removeEventListener("pointerenter",g)};return{clear:m,remove:()=>{m(),h(),l()}}}function wo(t,e,i,s){let o=document.createElement("div"),n=e.getBoundingClientRect(),r=t.getBoundingClientRect();o.style.position="fixed",c(o,"data-safe-area","");let u=(l,a)=>{if(n.top===0&&n.bottom===0)return;let f;n.left<r.left&&(f="left"),n.right>r.right&&(f="right"),n.top<r.top&&n.bottom<a&&(f="up"),n.bottom>r.bottom&&n.top>a&&(f="down"),f===void 0&&(f="right");let d,m,g,h,b,v,y,E,_=10;switch(f){case"left":d=n.right,m=Math.max(n.right,l)+5,g=m-d,h=Math.min(r.top,n.top)-_,b=Math.max(r.bottom,n.bottom)+_,v=b-h,y=a-h,E=`polygon(0% 0%, 100% ${y}px, 0% 100%)`;break;case"right":d=Math.min(n.left,l)-5,m=n.left,g=m-d,h=Math.min(r.top,n.top)-_,b=Math.max(r.bottom,n.bottom)+_,v=b-h,y=a-h,E=`polygon(0% ${y}px, 100% 0%, 100% 100%)`;break;case"up":d=Math.min(l,n.left)-_,m=Math.max(l,n.right)+_,g=m-d,h=n.bottom,b=Math.max(n.bottom,a)+5,v=b-h,y=l-d,E=`polygon(0% 0%, 100% 0%, ${y}px 100%)`;break;case"down":d=Math.min(l,n.left)-_,m=Math.max(l,n.right)+_,g=m-d,h=Math.min(n.top,a)-5,b=n.top,v=b-h,y=l-d,E=`polygon(${y}px 0%, 100% 100%, 0% 100%)`;break}o.style.left=`${d}px`,o.style.top=`${h}px`,o.style.width=`${g}px`,o.style.height=`${v}px`,o.style.clipPath=E};return{safeArea:o,redraw:(l,a)=>{o.isConnected||t.appendChild(o),u(l,a)},remove:()=>{o.remove()}}}function yo(t,e,i,s){return!Ao(t,i,s)&&!xo(e,i,s)}function Ao(t,e,i){return t.left<=e&&e<=t.right&&t.top<=i&&i<=t.bottom}function xo(t,e,i){return t.left<=e&&e<=t.right&&t.top<=i&&i<=t.bottom}function c(t,e,i){t._durableAttributeObserver===void 0&&(t._durableAttributeObserver=us(t,[e])),t._durableAttributeObserver.hasAttribute(e)||t._durableAttributeObserver.addAttribute(e),t._durableAttributeObserver.pause(()=>{t.setAttribute(e,i)})}function se(t,e){w(t,e),Eo(t,e)}function w(t,e){t._durableAttributeObserver===void 0&&(t._durableAttributeObserver=us(t,[e])),t._durableAttributeObserver.hasAttribute(e)||t._durableAttributeObserver.addAttribute(e),t._durableAttributeObserver.pause(()=>{t.removeAttribute(e)})}function Eo(t,e){t?._durableAttributeObserver?.hasAttribute(e)&&t._durableAttributeObserver.releaseAttribute(e)}function us(t,e){let i=o=>{o.forEach(n=>{n.oldValue===null?t._durableAttributeObserver.pause(()=>w(t,n.attributeName)):t._durableAttributeObserver.pause(()=>c(t,n.attributeName,n.oldValue))})},s=new MutationObserver(o=>i(o));return s.observe(t,{attributeFilter:e,attributeOldValue:!0}),{attributes:e,hasAttribute(o){return this.attributes.includes(o)},addAttribute(o){this.attributes.includes(o)||this.attributes.push(o),s.observe(t,{attributeFilter:this.attributes,attributeOldValue:!0})},releaseAttribute(o){this.hasAttribute(o)&&s.observe(t,{attributeFilter:this.attributes,attributeOldValue:!0})},pause(o){i(s.takeRecords()),s.disconnect(),o(),s.observe(t,{attributeFilter:this.attributes,attributeOldValue:!0})}}}function _o(){return{safeArea:{contains:()=>!1},redraw:()=>{},remove:()=>{}}}function So(t,e){let i;return(...s)=>{clearTimeout(i),i=setTimeout(()=>{t(...s)},e)}}var Le=0,De=!1;T(({css:t})=>t`[data-flux-allow-scroll] { pointer-events: auto; }`);function oe(t=null,e=!1,i=[]){if(e)return{lock:()=>{},unlock:()=>{}};let s=(n=!1)=>{ls(document.documentElement);let r={overflow:"hidden",...n?{pointerEvents:"none"}:{}};window.CSS&&CSS.supports&&CSS.supports("scrollbar-gutter: stable")?document.documentElement.scrollHeight>document.documentElement.clientHeight&&window.getComputedStyle(document.documentElement).overflowY!=="hidden"&&(r.scrollbarGutter="stable"):r.paddingRight=`calc(${window.innerWidth-document.documentElement.clientWidth}px + ${window.getComputedStyle(document.documentElement).paddingRight})`,ko(document.documentElement…38230 tokens truncated…lates.option,{text:i.el.textContent.trim(),default:o,value:e});n.setAttribute("data-value",e),n.setAttribute("data-appended",e),n.deselect=()=>i.deselect(),this.templates.option.after(n),this.templates.option.clearOption=()=>{n.remove(),this.templates.option.clearOption=()=>{}}}}renderPlaceholder(){if(!this.templates.placeholder)return;let e=je(this.templates.placeholder);e.setAttribute("data-appended",""),this.templates.placeholder.after(e),this.templates.placeholder.clearPlaceholder=()=>{e.remove(),this.templates.placeholder.clearPlaceholder=()=>{}}}renderOverflow(e,i){if(!this.templates.overflow)return;let o=je(this.templates.overflow,{remainder:i,count:this.selecteds.size});o.setAttribute("data-appended",""),this.templates.overflow.after(o),this.templates.overflow.clearOverflow=()=>{o.remove(),this.templates.placeholder.clearOverflow=()=>{}}}};function je(t,e={}){let i=t.content.cloneNode(!0);return Object.entries(e).forEach(([o,n])=>{(o==="default"?i.querySelectorAll("slot:not([name])"):i.querySelectorAll(`slot[name="${o}"]`)).forEach(r=>r.replaceWith(typeof n=="string"?document.createTextNode(n):n))}),i.firstElementChild}var Ke=class extends pt{boot(){let e=this.list();this._controllable=new z(this),this._selectable=new oe(e,{multiple:this.hasAttribute("multiple")}),this._controllable.initial(o=>o&&this._selectable.setState(o)),this._controllable.getter(()=>this._selectable.getState());let i=ee();this._controllable.setter(i(o=>{this._selectable.setState(o)})),this._selectable.onChange(i(()=>{this._controllable.dispatch(),this.dispatchEvent(new CustomEvent("select",{bubbles:!1}))}))}mount(){this._disableable=new ne(this);let e=this.input(),i=this.button(),o=this.list(),n=this.hasAttribute("multiple"),s=this.hasAttribute("autocomplete"),r=this.hasAttribute("autocomplete")&&this.getAttribute("autocomplete").trim().split(" ").includes("strict"),c=this.querySelector("ui-options")||this,a=An(c,"options");this._activatable=new $e(c,{filter:"data-hidden"}),!e&&!i&&this._disableable.onInitAndChange(u=>{u?this.removeAttribute("tabindex"):this.setAttribute("tabindex","0")}),this.hasAttribute("filter")&&this.getAttribute("filter")!=="manual"&&(this._filterable=new ze(o),this._filterable.onChange(()=>{this._activatable.clearActive(),this._filterable.hasResults()&&this._activatable.activateFirst()}),this.addEventListener("close",()=>{this._filterable&&this._filterable.filter("")}));let l=this.querySelector("[popover]:not(ui-tooltip > [popover])"),h=l?.querySelector("input"),f=this.querySelector("input");f=l?.contains(f)?null:f;let b=this.querySelector("button");if(b=l?.contains(b)?null:b,!(l||f))Ue(this,this._activatable),Ye(this,this,this._activatable),ao(this,this._activatable,this._selectable);else if(!l&&f){let u=f;this._disableable.onInitAndChange(d=>{d?u&&p(u,"disabled",""):u&&x(u,"disabled")}),Gt(this,u,this._selectable,this._popoverable),ao(u,this._activatable,this._selectable),uo(s,r,this,u,this._selectable,this._popoverable),Xt(u),Kt(u),this._filterable&&Yt(u,this._filterable),co(u,this._activatable),Ue(u,this._activatable),Ye(this,u,this._activatable),At(this,this._activatable)}else if(l&&f){let u=f;p(u,"role","combobox"),p(u,"aria-controls",a);let d=l;this._popoverable=new R(d),this._anchorable=new N(d,{reference:u,matchWidth:!0,position:this.hasAttribute("position")?this.getAttribute("position"):void 0,gap:this.hasAttribute("gap")?this.getAttribute("gap"):void 0,offset:this.hasAttribute("offset")?this.getAttribute("offset"):void 0}),uo(s,r,this,u,this._selectable,this._popoverable),this._disableable.onInitAndChange(v=>{v?u&&p(u,"disabled",""):u&&x(u,"disabled")}),this.querySelectorAll("button").forEach(v=>{d.contains(v)||(p(v,"tabindex","-1"),p(v,"aria-controls",a),p(v,"aria-haspopup","listbox"),xt(v,this._popoverable),w(v,"click",()=>{this._popoverable.toggle(),u.focus()}))}),Gt(this,u,this._selectable,this._popoverable),zt(this,u,d,this._popoverable,this._anchorable),Qt(this,this._popoverable),xt(u,this._popoverable),Xt(u),Kt(u),this._filterable&&Yt(u,this._filterable),co(u,this._activatable),En(u,this._popoverable),Ut(u,this._popoverable,this._activatable,this._selectable),xn(u,this._popoverable),Ue(u,this._activatable),Ye(this,u,this._activatable),At(this,this._activatable),jt(this._popoverable,this._activatable,this._selectable),Jt(this,this._selectable,this._popoverable,n)}else if(l&&h){let u=b,d=h,v=l;p(u,"role","combobox"),p(d,"role","combobox"),p(u,"aria-controls",a),this._disableable.onInitAndChange(g=>{g?(u&&p(u,"disabled",""),d&&p(d,"disabled","")):(u&&x(u,"disabled"),d&&x(d,"disabled"))}),this._popoverable=new R(v),this._anchorable=new N(v,{reference:u,matchWidth:!0,position:this.hasAttribute("position")?this.getAttribute("position"):void 0,gap:this.hasAttribute("gap")?this.getAttribute("gap"):void 0,offset:this.hasAttribute("offset")?this.getAttribute("offset"):void 0}),Xt(d),Kt(d),this._filterable&&Yt(d,this._filterable),Sn(d,this._popoverable),zt(this,u,v,this._popoverable,this._anchorable),Qt(this,this._popoverable),xt(u,this._popoverable),Gt(this,d,this._selectable,this._popoverable),Ut(u,this._popoverable,this._activatable,this._selectable),lo(u,this._popoverable),Ue(d,this._activatable),ro(u,this._activatable,this._popoverable),Ye(this,d,this._activatable),At(this,this._activatable),jt(this._popoverable,this._activatable,this._selectable),Jt(this,this._selectable,this._popoverable,n)}else if(l){let u=b,d=l;p(u,"role","combobox"),p(u,"aria-controls",a),this._disableable.onInitAndChange(v=>{v?(u&&p(u,"disabled",""),e&&p(e,"disabled","")):(u&&x(u,"disabled"),e&&x(e,"disabled"))}),this._popoverable=new R(d),this._anchorable=new N(d,{reference:u,matchWidth:!0,position:this.hasAttribute("position")?this.getAttribute("position"):void 0,gap:this.hasAttribute("gap")?this.getAttribute("gap"):void 0,offset:this.hasAttribute("offset")?this.getAttribute("offset"):void 0}),zt(this,u,d,this._popoverable,this._anchorable),Qt(this,this._popoverable),xt(u,this._popoverable),Ut(u,this._popoverable,this._activatable,this._selectable),lo(u,this._popoverable),Ue(u,this._activatable),ro(u,this._activatable,this._popoverable),Ye(this,u,this._activatable),At(this,this._activatable),jt(this._popoverable,this._activatable,this._selectable),Jt(this,this._selectable,this._popoverable,n)}new MutationObserver(()=>{setTimeout(()=>{if(!this._popoverable||this._popoverable.getState()){let u=this._selectable.selecteds()[0]?.el;setTimeout(()=>{this._activatable.activateSelectedOrFirst(u)})}else this._activatable.clearActive()})}).observe(o,{childList:!0})}button(){return Array.from(this.querySelectorAll("button")).find(e=>e.nextElementSibling?.matches("[popover]"))||null}input(){return this.querySelector("input")}list(){return this.querySelector("ui-options")||this}clear(){this.input()&&(this.input().value="",this.input().dispatchEvent(new Event("input",{bubbles:!1})))}open(){this._popoverable.setState(!0)}close(){this._popoverable.setState(!1)}deselectLast(){!this.hasAttribute("multiple")&&this.value!==null&&(this.value=null,this.dispatchEvent(new Event("input",{bubbles:!1})),this.dispatchEvent(new Event("change",{bubbles:!1}))),this.hasAttribute("multiple")&&this.value.length!==0&&(this.value=this.value.slice(0,-1),this.dispatchEvent(new Event("input",{bubbles:!1})),this.dispatchEvent(new Event("change",{bubbles:!1})))}},Zt=class extends k{boot(){p(this,"data-hidden","")}mount(){queueMicrotask(()=>{let e=this.closest("ui-autocomplete, ui-combobox, ui-select"),i=this.closest("ui-options");if(!i)return;let o=c=>c.hasAttribute("data-hidden"),n=()=>{let c;CSS.supports("selector(&)")?c=Array.from(i.querySelectorAll("& > ui-option")).filter(a=>!o(a)).length===0:c=Array.from(i.querySelectorAll(":scope > ui-option")).filter(a=>!o(a)).length===0,c?x(this,"data-hidden"):p(this,"data-hidden","")};n();let s=e._filterable;s&&s.onChange(n),new MutationObserver(c=>{setTimeout(()=>n())}).observe(i,{childList:!0})})}};T("selected",yt);T("select",Ke);T("empty",Zt);Z(({css:t})=>t`ui-select { display: block; }`);Z(({css:t})=>t`ui-selected-option { display: contents; }`);Z(({css:t})=>t`ui-empty { display: block; cursor: default; }`);function Ue(t,e){w(t,"keydown",i=>{["ArrowDown","ArrowUp","Escape"].includes(i.key)&&(i.key==="ArrowDown"?(e.activateNext(),i.preventDefault(),i.stopPropagation()):i.key==="ArrowUp"&&(e.activatePrev(),i.preventDefault(),i.stopPropagation()))})}function ro(t,e,i){ft(t,o=>{e.activateBySearch(o),i.getState()||e.getActive()?.click()})}function Ye(t,e,i){w(e,"keydown",o=>{if(o.key==="Enter"){let n=i.getActive();if(o.preventDefault(),o.stopPropagation(),!n)return;n.click(),t.dispatchEvent(new CustomEvent("action",{bubbles:!1,cancelable:!1}))}})}function At(t,e,i=!1){w(t,i?"pointerdown":"click",o=>{if(o.target.closest("ui-option")){let n=o.target.closest("ui-option");if(n._disabled)return;n._selectable?.trigger(),t.dispatchEvent(new CustomEvent("action",{bubbles:!1,cancelable:!1})),o.preventDefault(),o.stopPropagation()}})}function ao(t,e,i){w(t,"focus",()=>{let o=i.selecteds()[0]?.el;e.activateSelectedOrFirst(o)}),w(t,"blur",()=>{e.clearActive()})}function An(t){let e=q(t,"options");return p(t,"role","listbox"),e}function xt(t,e){p(t,"aria-haspopup","listbox");let i=()=>{p(t,"aria-expanded",e.getState()?"true":"false"),e.getState()?p(t,"data-open",""):x(t,"data-open","")};e.onChange(()=>{i()}),i()}function zt(t,e,i,o,n){let s=()=>{Array.from([t,i]).forEach(r=>{o.getState()?p(r,"data-open",""):x(r,"data-open","")}),o.getState()?n.reposition():n.cleanup()};o.onChange(()=>s()),s(),o.onChange(()=>{o.getState()?t.dispatchEvent(new Event("open",{bubbles:!1,cancelable:!1})):t.dispatchEvent(new Event("close",{bubbles:!1,cancelable:!1}))})}function jt(t,e,i){t.onChange(()=>{if(t.getState()){let o=i.selecteds()[0]?.el;setTimeout(()=>{e.activateSelectedOrFirst(o)})}else e.clearActive()})}function Ut(t,e){w(t,"keydown",i=>{["ArrowDown","ArrowUp","Escape"].includes(i.key)&&(i.key==="ArrowDown"||i.key==="ArrowUp"?e.getState()||(e.setState(!0),i.preventDefault(),i.stopImmediatePropagation()):i.key==="Escape"&&e.getState()&&(e.setState(!1),i.preventDefault(),i.stopImmediatePropagation()))})}function xn(t,e){w(t,"click",()=>{e.getState()||(e.setState(!0),t.focus())})}function lo(t,e){w(t,"click",()=>{e.setState(!e.getState()),t.focus()})}function Sn(t,e){e.onChange(()=>{e.getState()&&setTimeout(()=>t.focus())})}function Yt(t,e){e&&w(t,"input",i=>{e.filter(i.target.value)})}function Kt(t){w(t,"focus",()=>t.select())}function Xt(t){w(t,"change",e=>e.stopPropagation()),w(t,"input",e=>e.stopPropagation())}function En(t,e){w(t,"keydown",i=>{(/^[a-zA-Z0-9]$/.test(i.key)||i.key==="Backspace")&&(e.getState()||e.setState(!0))})}function Gt(t,e,i,o){if(!t.hasAttribute("clear"))return;let s=f=>{e.value=f,e.dispatchEvent(new Event("input",{bubbles:!1}))},r=t.getAttribute("clear"),c=r===""||r.split(" ").includes("action"),a=r===""||r.split(" ").includes("select"),l=r===""||r.split(" ").includes("close"),h=r===""||r.split(" ").includes("esc");r==="none"&&(c=a=l=h=!1),c?t.addEventListener("action",f=>{s("")}):a&&i.onChange(()=>{queueMicrotask(()=>s(""))}),l&&o.onChange(()=>{o.getState()||s("")}),h&&w(e,"keydown",f=>{f.key==="Escape"&&s("")})}function Jt(t,e,i,o){let n=!o,s=!o;if(t.hasAttribute("close")){let r=t.getAttribute("close");n=r===""||r.split(" ").includes("action"),s=r.split(" ").includes("select"),r==="none"&&(n=s=!1)}n?t.addEventListener("action",r=>{i.setState(!1)}):s&&e.onChange(()=>{i.setState(!1)})}function co(t,e){e.onChange(()=>{let i=e.getActive();i?p(t,"aria-activedescendant",i.id):x(t,"aria-activedescendant")})}function uo(t,e,i,o,n,s){if(!t){p(o,"autocomplete","off"),p(o,"aria-autocomplete","none");return}let r=c=>{o.value=c,o.dispatchEvent(new Event("input",{bubbles:!1}))};p(o,"autocomplete","off"),p(o,"aria-autocomplete","list"),n.setState(o.value),queueMicrotask(()=>{n.onInitAndChange(()=>{o.value=n.selectedTextValue()})}),i.addEventListener("action",c=>{r(n.selectedTextValue())}),e&&s.onChange(()=>{s.getState()||r(n.selectedTextValue())})}function Qt(t,e){let{lock:i,unlock:o}=ht();e.onChange(()=>{e.getState()?i():o()})}var ei=class t extends k{boot(){if(this._focusable=new qe(this,{wrap:!1,ensureTabbable:!1}),w(this,"keydown",e=>{["ArrowDown"].includes(e.key)?(e.target===this?this._focusable.focusFirst():this._focusable.focusNext(),e.preventDefault(),e.stopPropagation()):["ArrowUp"].includes(e.key)&&(e.target===this?this._focusable.focusFirst():this._focusable.focusPrev(),e.preventDefault(),e.stopPropagation())}),ft(this,e=>this._focusable.focusBySearch(e)),this.hasAttribute("popover")&&this.addEventListener("lofi-close-popovers",()=>{setTimeout(()=>this.hidePopover(),50)}),this.parentElement.localName==="ui-dropdown"){let e=this.parentElement;w(e.trigger(),"keydown",i=>{i.key==="ArrowDown"&&(this.fromArrowDown=!0,this.showPopover(),i.preventDefault(),i.stopPropagation())})}p(this,"role","menu"),p(this,"tabindex","-1")}mount(){this.initializeMenuItems(),new MutationObserver(i=>{this.initializeMenuItems()}).observe(this,{childList:!0,subtree:!0})}onPopoverShow(){queueMicrotask(()=>{this.fromArrowDown?(this._focusable.focusFirst(),this.fromArrowDown=!1):this.focus()})}onPopoverHide(){this._focusable.wipeTabbables()}initializeMenuItems(){this.walker().each(e=>{e._disableable||kn(e)})}walker(){return V(this,(e,{skip:i,reject:o})=>{if(e instanceof t||e instanceof Ke)return o();if(!["a","button"].includes(e.localName))return i()})}},ti=class extends k{boot(){}},ii=class extends k{boot(){this._disabled=this.hasAttribute("disabled"),this._disableable=new ne(this);let e=this;if(this._disabled&&(p(e,"disabled",""),p(e,"aria-disabled","true")),q(e,"menu-checkbox"),p(e,"role","menuitemcheckbox"),this._disabled)return;e._focusable=new L(e,{disableable:this._disableable,hover:!0,tabbableAttr:"data-active"}),e._selectable=new _(e,{toggleable:!0,value:this.hasAttribute("value")?this.getAttribute("value"):e.textContent.trim(),label:this.hasAttribute("label")?this.getAttribute("label"):e.textContent.trim(),dataAttr:"data-checked",ariaAttr:"aria-checked",selectedInitially:this.hasAttribute("checked")}),this._controllable=new z(this),this._controllable.initial(o=>o&&e._selectable.setState(o)),this._controllable.getter(()=>e._selectable.getState());let i=ee();this._controllable.setter(i(o=>{this._selectable.setState(o)})),this._selectable.onChange(i(()=>{this._controllable.dispatch()})),w(e,"click",()=>{this.dispatchEvent(new CustomEvent("lofi-close-popovers",{bubbles:!0})),e._selectable.press()}),ri(e)}},oi=class extends k{boot(){this._disabled=this.hasAttribute("disabled"),this._disableable=new ne(this);let e=this;this._disabled&&(p(e,"disabled",""),p(e,"aria-disabled","true")),q(e,"menu-radio"),p(e,"role","menuitemradio"),!this._disabled&&(e._focusable=new L(e,{disableable:this._disableable,hover:!0,tabbableAttr:"data-active"}),e._selectable=new _(e,{toggleable:!1,value:this.hasAttribute("value")?this.getAttribute("value"):e.textContent.trim(),label:this.hasAttribute("label")?this.getAttribute("label"):e.textContent.trim(),dataAttr:"data-checked",ariaAttr:"aria-checked",selectedInitially:this.hasAttribute("checked")}),w(e,"click",()=>{this.dispatchEvent(new CustomEvent("lofi-close-popovers",{bubbles:!0})),e._selectable.press()}),ri(e))}},ni=class extends k{boot(){this._selectable=new oe(this),this._controllable=new z(this),p(this,"role","group"),this._controllable.initial(i=>i&&this._selectable.setState(i)),this._controllable.getter(()=>this._selectable.getState());let e=ee();this._controllable.setter(e(i=>{this._selectable.setState(i)})),this._selectable.onChange(e(()=>{this._controllable.dispatch()}))}},si=class extends k{boot(){this._selectable=new oe(this,{multiple:!0}),this._controllable=new z(this),p(this,"role","group"),this._controllable.initial(i=>i&&this._selectable.setState(i)),this._controllable.getter(()=>this._selectable.getState());let e=ee();this._controllable.setter(e(i=>{this._selectable.setState(i)})),this._selectable.onChange(e(()=>{this._controllable.dispatch()}))}};Z(({css:t})=>t`ui-menu[popover]:popover-open { display: block; }`);Z(({css:t})=>t`ui-menu[popover].\:popover-open { display: block; }`);Z(({css:t})=>t`ui-menu-checkbox, ui-menu-radio { cursor: default; display: contents; }`);T("menu",ei);T("submenu",ti);T("menu-checkbox",ii);T("menu-radio",oi);T("menu-radio-group",ni);T("menu-checkbox-group",si);function ri(t){w(t,"keydown",e=>{e.key==="Enter"&&(t.click(),e.preventDefault(),e.stopPropagation())}),w(t,"keydown",e=>{e.key===" "&&(e.preventDefault(),e.stopPropagation())}),w(t,"keyup",e=>{e.key===" "&&(t.click(),e.preventDefault(),e.stopPropagation())})}function kn(t){t._disableable=new ne(t),t._disabled=t.hasAttribute("disabled");let e=t.querySelector("a"),i=t,o=t.parentElement.matches("ui-submenu")&&t.parentElement.querySelector("ui-menu[popover]"),n=e||i;if(t._disabled&&(p(n,"disabled",""),p(n,"aria-disabled","true")),q(n,"menu-item"),p(n,"role","menuitem"),!t._disabled)if(n._focusable=new L(n,{disableable:t._disableable,hover:!0,tabbableAttr:"data-active"}),!o)t.hasAttribute("disabled")||w(t,"click",()=>{t.dispatchEvent(new CustomEvent("lofi-close-popovers",{bubbles:!0}))}),ri(i);else{o._popoverable=new R(o,{triggers:[i]}),o._anchorable=new N(o,{reference:i,position:o.hasAttribute("position")?o.getAttribute("position"):"right start",gap:o.hasAttribute("gap")?o.getAttribute("gap"):"-5",crossAxis:!0}),i.addEventListener("click",r=>{o._popoverable.setState(!0)});let{clear:s}=Se(i,o,{gain(){o._popoverable.setState(!0)},lose(){o._popoverable.setState(!1)},focusable:!1});o._popoverable.onChange(()=>{o._popoverable.getState()||(s(),o._focusable.wipeTabbables()),o._popoverable.getState()?o._anchorable.reposition():o._anchorable.cleanup()}),w(i,"keydown",r=>{r.key==="Enter"&&(o._popoverable.setState(!0),setTimeout(()=>o._focusable.focusFirst()))}),w(i,"keydown",r=>{r.key==="ArrowRight"&&(o._popoverable.setState(!0),setTimeout(()=>o._focusable.focusFirst()))}),w(o,"keydown",r=>{r.key==="ArrowLeft"&&(o._popoverable.setState(!1),i.focus(),r.stopPropagation())})}}document.addEventListener("alpine:init",()=>{let t=window.Flux?.applyAppearance;t||(t=()=>{window.Flux.appearance=null,window.localStorage.removeItem("flux.appearance")});let e=Alpine.reactive({toast(...o){let n={slots:{},dataset:{}};typeof o[0]=="string"&&(n.slots.text=o.shift()),typeof o[0]=="string"&&(n.slots.heading=n.slots.text,n.slots.text=o.shift());let s=o.shift()||{};s.text&&(n.slots.text=s.text),s.heading&&(n.slots.heading=s.heading),s.variant&&(n.dataset.variant=s.variant),s.position&&(n.dataset.position=s.position),s.duration!==void 0&&(n.duration=s.duration),document.dispatchEvent(new CustomEvent("toast-show",{detail:n}))},modal(o){return{show(){document.dispatchEvent(new CustomEvent("modal-show",{detail:{name:o}}))},close(){document.dispatchEvent(new CustomEvent("modal-close",{detail:{name:o}}))}}},modals(){return{close(){document.dispatchEvent(new CustomEvent("modal-close",{detail:{}}))}}},appearance:window.localStorage.getItem("flux.appearance")||"system",systemAppearanceChanged:1,get dark(){return JSON.stringify(e.systemAppearanceChanged),e.appearance==="system"?window.matchMedia("(prefers-color-scheme: dark)").matches:e.appearance==="dark"},set dark(o){let n=this.dark;o!==n&&(o?e.appearance="dark":e.appearance="light")}});window.Flux=e,Alpine.magic("flux",()=>e),Alpine.effect(()=>{t(e.appearance)}),document.addEventListener("livewire:navigated",()=>{t(e.appearance)}),window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change",()=>{e.systemAppearanceChanged++,t(e.appearance)})});!Ei()&&!ki()&&_i();})();
vendor/livewire/flux/stubs/resources/views/flux/modal/close.blade.php:3:<ui-close data-flux-modal-close {{ $attributes }}>
vendor/livewire/flux/stubs/resources/views/flux/modal/close.blade.php:5:</ui-close>
vendor/livewire/flux/stubs/resources/views/flux/modal/index.blade.php:95:        x-on:modal-close.document="handleClose($event)"


Output:
Chunk ID: 8d957c
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 2467
Output:
<!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>
                    <flux:modal.trigger name="mobile-navigation">
                        <flux:button variant="subtle" icon="bars-3" aria-label="Open menu" class="md:hidden" data-test="mobile-menu-button" />
                    </flux:modal.trigger>

        <flux:modal name="mobile-navigation" flyout position="left" class="w-80 max-w-[calc(100vw-2rem)] md:hidden">

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

                    @foreach ($mainLinks as $item)
                        <flux:modal.close>

                                {{ $item['label'] }}

                        </flux:modal.close>

                        @foreach (($item['children'] ?? []) as $child)
                            <flux:modal.close>

                                    {{ $child['label'] }}

                            </flux:modal.close>
                        @endforeach
                    @endforeach

                        <flux:modal.close>

                                Search

                        </flux:modal.close>

                        <flux:modal.close>

                                Account

                        </flux:modal.close>

        </flux:modal>

        <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: 9b7376
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 883
Output:
diff --git a/resources/views/layouts/storefront.blade.php b/resources/views/layouts/storefront.blade.php
index 4f9c5c0a..3e9d2f14 100644
--- a/resources/views/layouts/storefront.blade.php
+++ b/resources/views/layouts/storefront.blade.php
@@ -65,10 +65,55 @@
                     <flux:modal.trigger name="cart-drawer">
                         <flux:button variant="subtle" icon="shopping-bag" aria-label="Cart" />
                     </flux:modal.trigger>
+                    <flux:modal.trigger name="mobile-navigation">
+                        <flux:button variant="subtle" icon="bars-3" aria-label="Open menu" class="md:hidden" data-test="mobile-menu-button" />
+                    </flux:modal.trigger>

+        <flux:modal name="mobile-navigation" flyout position="left" class="w-80 max-w-[calc(100vw-2rem)] md:hidden">
+

+

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

+

+
+
+                    @foreach ($mainLinks as $item)
+                        <flux:modal.close>
+
+                                {{ $item['label'] }}
+
+                        </flux:modal.close>
+
+                        @foreach (($item['children'] ?? []) as $child)
+                            <flux:modal.close>
+
+                                    {{ $child['label'] }}
+
+                            </flux:modal.close>
+                        @endforeach
+                    @endforeach
+
+
+

+

+                        <flux:modal.close>
+
+                                Search
+
+                        </flux:modal.close>
+
+                        <flux:modal.close>
+
+                                Account
+
+                        </flux:modal.close>
+

+

+

+        </flux:modal>
+
         <livewire:storefront.cart-drawer />


Input:
*** Begin Patch
*** Update File: resources/views/layouts/storefront.blade.php
@@

                     @foreach ($mainLinks as $item)
-                        <flux:modal.close>
-
-                                {{ $item['label'] }}
-
-                        </flux:modal.close>
+
+                            {{ $item['label'] }}
+

                         @foreach (($item['children'] ?? []) as $child)
-                            <flux:modal.close>
-
-                                    {{ $child['label'] }}
-
-                            </flux:modal.close>
+
+                                {{ $child['label'] }}
+
                         @endforeach
                     @endforeach

-                        <flux:modal.close>
-
-                                Search
-
-                        </flux:modal.close>
+
+                            Search
+

-                        <flux:modal.close>
-
-                                Account
-
-                        </flux:modal.close>
+
+                            Account
+

*** End Patch


Success. Updated the following files:
M resources/views/layouts/storefront.blade.php

@@ -82,14 +82,10 @@
                     @foreach ($mainLinks as $item)
-                        <flux:modal.close>
-
-                                {{ $item['label'] }}
-
-                        </flux:modal.close>
+
+                            {{ $item['label'] }}
+

                         @foreach (($item['children'] ?? []) as $child)
-                            <flux:modal.close>
-
-                                    {{ $child['label'] }}
-
-                            </flux:modal.close>
+
+                                {{ $child['label'] }}
+
                         @endforeach
@@ -100,13 +96,9 @@

-                        <flux:modal.close>
-
-                                Search
-
-                        </flux:modal.close>
+
+                            Search
+

-                        <flux:modal.close>
-
-                                Account
-
-                        </flux:modal.close>
+
+                            Account
+


Output: {"output":"Success. Updated the following files:\nM resources/views/layouts/storefront.blade.php\n","metadata":{"exit_code":0,"duration_seconds":0.0}}


I removed the problematic close wrappers from the flyout links. I’m rerunning the focused responsive suite now to verify the JavaScript errors are gone and to catch any remaining viewport-specific selector issues.


php artisan test --compact tests/Browser/Storefront/ResponsiveTest.php


Output:
Chunk ID: 7f17df
Wall time: 1.0018 seconds
Process running with session ID 54785
Original token count: 0
Output:


Output:
Chunk ID: 131dff
Wall time: 30.0006 seconds
Process running with session ID 54785
Original token count: 0
Output:


  ........

  Tests:    8 passed (46 assertions)
  Duration: 38.60s


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

  ........

  Tests:    8 passed (46 assertions)
  Duration: 38.60s


The focused Suite 13 browser tests now pass: 8 tests, 46 assertions. I’m checking the frontend build next because the layout introduced new Tailwind classes in Blade.


Output:
Chunk ID: 8de435
Wall time: 0.9864 seconds
Process exited with code 0
Original token count: 105
Output:

> build
> vite build

vite v7.3.1 building client environment for production...
transforming...
✓ 2 modules transformed.
Generated an empty chunk: "app".
rendering chunks...
computing gzip size...
public/build/manifest.json              0.33 kB │ gzip:  0.17 kB
public/build/assets/app-CHVevmNs.css  229.51 kB │ gzip: 31.52 kB
public/build/assets/app-l0sNRNKZ.js     0.00 kB │ gzip:  0.02 kB
✓ built in 499ms


> build
> vite build

vite v7.3.1 building client environment for production...
transforming...
✓ 2 modules transformed.
Generated an empty chunk: "app".
rendering chunks...
computing gzip size...
public/build/manifest.json              0.33 kB │ gzip:  0.17 kB
public/build/assets/app-CHVevmNs.css  229.51 kB │ gzip: 31.52 kB
public/build/assets/app-l0sNRNKZ.js     0.00 kB │ gzip:  0.02 kB
✓ built in 499ms


find .playwright-mcp -maxdepth 2 -type f 2>/dev/null | sort


ls -la tests/Browser/Screenshots 2>/dev/null | sed -n '1,120p'


 resources/views/layouts/storefront.blade.php | 37 ++++++++++++++++++++++++++++
 1 file changed, 37 insertions(+)


 M resources/views/layouts/storefront.blade.php
?? .playwright-mcp/console-2026-05-03T23-28-42-443Z.log
?? .playwright-mcp/console-2026-05-04T04-24-15-455Z.log
?? .playwright-mcp/console-2026-05-04T05-58-46-727Z.log
?? tests/Browser/Storefront/ResponsiveTest.php


/bin/zsh
-lc
sed -n '1,220p' specs/progress.md

# Shop Build Progress

## Objective

Build a complete, self-contained Laravel shop system from `specs/*`, with implemented acceptance criteria mapped to evidence, passing automated checks, verified browser flows, independent QA, and meaningful commits.

## Current Status

- Status: in progress
- Active slice: Phase 8 - final verification and completion audit
- Started from: Laravel Livewire/Fortify starter kit with no shop domain tables or models present
- Last updated: 2026-05-04

## Phased Plan

1. Foundation: SQLite config, tenancy tables/models/factories/seeders, store resolution middleware, store scoping, admin/customer auth foundations, policies, and Phase 1 tests.
2. Catalog: product, variant, inventory, collection, media schema/models/services, admin catalog CRUD, storefront browsing, and tests.
3. Storefront theme/layout: theme/page/navigation data, storefront layout, product/collection/search/page views, and smoke browser verification.
4. Cart/checkout/pricing: cart API/UI, discount, shipping, tax, checkout state machine, and tests.
5. Payments/orders/fulfillment: mock PSP, order creation, refunds, fulfillments, customer account order views, and tests.
6. Admin panel: dashboard and all admin list/form/detail pages from Spec 03, with Livewire/Flux interactions.
7. Search/analytics/apps/webhooks: SQLite search, event aggregation, developer token/webhook UI, delivery jobs, and tests.
8. Final verification: full Pest suite, Pint, frontend build, Playwright MCP customer/admin flows, independent QA, fixes, and completion audit.

## Acceptance Checklist

| Area | Criteria Source | Status | Evidence |
| --- | --- | --- | --- |
| Database schema | `specs/01-DATABASE-SCHEMA.md` | partial | Phase 1 tenancy/auth tables, Phase 2 catalog tables, Phase 3 theme/content/navigation tables including `navigation_items.parent_id` nesting, Phase 4 cart/checkout/pricing tables, Phase 5 order/payment/fulfillment tables, and Phase 7 search/analytics/apps/webhooks/export tables are implemented: search_settings, search_queries, SQLite FTS5 `products_fts`, analytics_events, analytics_daily, apps, app_installations, oauth_clients, oauth_tokens, webhook_subscriptions, webhook_deliveries, and data_exports. |
| Routes/API | `specs/02-API-ROUTES.md` | partial | Phase 1 auth routes plus catalog/cart/order/customer/discount/content/settings/search/analytics/apps/developers UI routes exist: `/`, `/collections`, `/collections/{handle}`, `/products/{handle}`, `/cart`, `/checkout`, `/checkout/confirmation/{order}`, `/search`, `/pages/{handle}`, `/account/login`, `/account/register`, `/forgot-password`, `/reset-password/{token}`, `/account/forgot-password`, `/account/reset-password/{token}`, `/account`, `/account/orders`, `/account/orders/{order}`, `/account/addresses`, `/account/logout`, `/admin/login`, `/admin/forgot-password`, `/admin/reset-password/{token}`, `/admin/products`, `/admin/products/create`, `/admin/products/{product}/edit`, `/admin/collections`, `/admin/collections/create`, `/admin/collections/{collection}/edit`, `/admin/inventory`, `/admin/orders`, `/admin/orders/{order}`, `/admin/customers`, `/admin/customers/{customer}`, `/admin/discounts`, `/admin/discounts/create`, `/admin/discounts/{discount}/edit`, `/admin/pages`, `/admin/pages/create`, `/admin/pages/{page}/edit`, `/admin/navigation`, `/admin/themes`, `/admin/themes/{theme}/editor`, `/admin/settings`, `/admin/settings/shipping`, `/admin/settings/taxes`, `/admin/settings/checkout`, `/admin/settings/notifications`, `/admin/search/settings`, `/admin/analytics`, `/admin/apps`, `/admin/apps/{installation}`, and `/admin/developers`. Storefront REST API now includes cart line CRUD, checkout address/shipping/discount/payment-method selection, checkout payment completion, token-gated order lookup, product search, search suggestions, and analytics event ingestion under `/api/storefront/v1`. Admin REST API now includes store-scoped product/customer/order list/detail, collection CRUD, discount CRUD, content page CRUD, store settings read/update, search status/reindex endpoints, analytics summary, order export creation/status, shipping zone/rate settings, tax settings read/update, theme upload/publish/settings update, fulfillment creation, and refund creation under `/api/admin/v1/stores/{store}` with session or scoped bearer-token authentication. `/oauth/authorize`, `/oauth/token`, and `/api/apps/v1/stores/{store}` app routes are registered as explicit `501 Not Implemented` stubs per the deferred OAuth/app ecosystem scope note. |
| Admin UI | `specs/03-ADMIN-UI.md` | partial | Flux/Livewire admin shell now includes dashboard KPI/date-range reporting, analytics reporting with filters/export, catalog/product/collection/inventory management, product option matrix generation, product media upload/alt text/reorder/delete controls, order/customer/discount management, page index/form, nested navigation menu editor with drag sorting and parent selection, theme grid, theme settings edi…33296 chars truncated…esting docs, Flux UI input/table/badge docs, and Pest 4 JSON testing docs before the search changes.
- 2026-05-04: `php artisan make:model`, `make:migration`, `make:class`, `make:observer`, `make:controller`, `make:request`, `make:resource`, `make:livewire`, and `make:test --pest` created the search settings/query models, FTS migration, search service, product observer, storefront search API classes, admin search settings component, and search tests.
- 2026-05-04: `vendor/bin/pint --dirty --format agent` passed after the search changes.
- 2026-05-04: `php artisan test --compact tests/Feature/Search/SearchServiceTest.php tests/Feature/Api/StorefrontSearchApiTest.php tests/Feature/Admin/SearchSettingsTest.php` passed: 10 tests, 46 assertions.
- 2026-05-04: `php artisan test --compact tests/Feature/Api tests/Feature/Search tests/Feature/Admin/SearchSettingsTest.php tests/Feature/Catalog/CatalogUiTest.php` passed: 25 tests, 201 assertions.
- 2026-05-04: `php artisan route:list --path=api/storefront/v1/search --except-vendor` confirmed `GET /api/storefront/v1/search` and `GET /api/storefront/v1/search/suggest`; `php artisan route:list --path=admin/search` confirmed `GET /admin/search/settings`.
- 2026-05-04: `npm run build` passed after the search Blade/sidebar changes.
- 2026-05-04: `php artisan test --compact` passed after the search changes: 166 tests, 846 assertions.
- 2026-05-04: `php artisan migrate:fresh --seed --no-interaction` passed after the search changes; Boost query counts after browser smoke: search_settings 2, search_queries 2, products_fts 25.
- 2026-05-04: `mcp__laravel_boost__.get_absolute_url` resolved `http://shop.test/search?q=Cotton`, `http://shop.test/api/storefront/v1/search?q=Cotton`, and `http://shop.test/admin/search/settings` before browser verification.
- 2026-05-04: Playwright MCP verified the SQLite-backed storefront search page shows 5 Cotton results with filters, the storefront search API returns JSON product/facet data for Cotton, and admin search settings renders and rebuilds the index for 20 products; current Playwright console checks reported no warnings/errors. Boost `browser_logs` only returned older May 3 account-login warnings from a previously fixed issue.
- 2026-05-04: `mcp__laravel_boost__.search_docs` consulted Laravel 12 migration/validation/rate limiting/scheduling/aggregation docs, Livewire 4 form/testing docs, Flux UI select/button/table/badge docs, and Pest 4 docs before the analytics changes.
- 2026-05-04: `php artisan make:enum`, `make:model`, `make:seeder`, `make:class`, `make:job`, `make:controller`, `make:request`, `make:livewire`, and `make:test --pest` created the analytics event enum, models, migrations, seeders, service, aggregation job, storefront analytics API classes, admin analytics component, and analytics tests.
- 2026-05-04: `vendor/bin/pint --dirty --format agent` fixed import/order spacing after the analytics changes.
- 2026-05-04: `php artisan test --compact tests/Feature/Analytics/AnalyticsServiceTest.php tests/Feature/Api/StorefrontAnalyticsApiTest.php tests/Feature/Admin/AnalyticsDashboardTest.php` passed: 7 tests, 35 assertions.
- 2026-05-04: `php artisan route:list --path=api/storefront/v1/analytics --except-vendor` confirmed `POST /api/storefront/v1/analytics/events`; `php artisan route:list --path=admin/analytics` confirmed `GET /admin/analytics`.
- 2026-05-04: `php artisan schedule:list` confirmed `App\Jobs\AggregateAnalytics` is scheduled daily at 01:00 UTC.
- 2026-05-04: `php artisan test --compact tests/Feature/Api tests/Feature/Analytics tests/Feature/Admin/AnalyticsDashboardTest.php tests/Feature/Admin/DashboardTest.php` passed: 24 tests, 185 assertions.
- 2026-05-04: `npm run build` passed after the analytics Blade/sidebar changes.
- 2026-05-04: `php artisan migrate:fresh --seed --no-interaction` passed after the analytics changes.
- 2026-05-04: `php artisan test --compact` passed after the analytics changes: 173 tests, 881 assertions.
- 2026-05-04: `mcp__laravel_boost__.get_absolute_url` resolved `http://shop.test/admin/analytics` and `http://shop.test/api/storefront/v1/analytics/events` before browser verification.
- 2026-05-04: Playwright MCP verified `http://shop.test/admin/analytics` renders metrics, charts, top referrers, and CSV export; browser fetch to `/api/storefront/v1/analytics/events` returned 202 with 1 accepted event and current Playwright console checks reported no warnings/errors. Boost `browser_logs` only returned older May 3 account-login warnings from a previously fixed issue.
- 2026-05-04: `php artisan migrate:fresh --seed --no-interaction` passed after browser verification to restore seeded fixtures; Boost query counts after reset: analytics_daily 60, analytics_events 210, search_settings 2, products_fts 25.
- 2026-05-04: `mcp__laravel_boost__.search_docs` consulted Laravel 12 HTTP client, queue, validation, events, and testing docs plus Livewire 4, Flux UI, and Pest 4 docs before the apps/webhooks changes.


Output:
Chunk ID: 8591ee
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 66
Output:
 M resources/views/layouts/storefront.blade.php
?? .playwright-mcp/console-2026-05-03T23-28-42-443Z.log
?? .playwright-mcp/console-2026-05-04T04-24-15-455Z.log
?? .playwright-mcp/console-2026-05-04T05-58-46-727Z.log
?? tests/Browser/Storefront/ResponsiveTest.php


Output:
Chunk ID: 113746
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 29
Output:
 resources/views/layouts/storefront.blade.php | 37 ++++++++++++++++++++++++++++
 1 file changed, 37 insertions(+)