Output:
Chunk ID: 9db3cb
Wall time: 0.3235 seconds
Process exited with code 0
Original token count: 95
Output:
| shop                                    | http://shop.test                                    | /Users/fabianwesner/Herd/shop/                                    |         | 8.4         |
| shop_tmp                                | http://shop_tmp.test                                | /Users/fabianwesner/Herd/shop_tmp/                                |         | 8.4         |


/bin/zsh
-lc
herd sites | rg "shop|acme"

| shop                                    | http://shop.test                                    | /Users/fabianwesner/Herd/shop/                                    |         | 8.4         |
| shop_tmp                                | http://shop_tmp.test                                | /Users/fabianwesner/Herd/shop_tmp/                                |         | 8.4         |


Output:
Chunk ID: aa2422
Wall time: 0.1276 seconds
Process exited with code 0
Original token count: 1217
Output:
Herd 1.28.0

Usage:
  command [options] [arguments]

Options:
  -h, --help            Display help for the given command. When no command is given display help for the list command
  -q, --quiet           Do not output any message
  -V, --version         Display this application version
      --ansi|--no-ansi  Force (or disable --no-ansi) ANSI output
  -n, --no-interaction  Do not ask any interactive question
  -v|vv|vvv, --verbose  Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug

Available commands:
  completion          Dump the shell completion script
  composer            Proxy Composer commands with isolated site's PHP executable
  coverage            Proxy PHP commands with isolated site's PHP executable and Xdebug code coverage enabled
  db                  Open the database for the current or given site, if credentials are found.
  debug               Proxy PHP commands with isolated site's PHP executable and debug.ini loaded
  directory-listing   Determine directory-listing behavior. Default is off, which means a 404 will display.
  edit                Open the site in your favorite IDE
  fetch-share-url     Get the URL to the current share tunnel for Expose
  forget              [unpark] Remove the current working (or specified) directory from Herd's list of paths
  help                Display help for a command
  ini                 Open the php.ini in your favorite IDE
  init                Start the services defined in the Herd manifest file, and apply the configuration.
  isolate             Change the version of PHP used by Herd to serve the current working directory
  isolate-node        Change the version of Node used by the CLI for the current working directory
  isolated            List all sites using isolated versions of PHP.
  isolated-node       List all sites using isolated versions of Node.
  link                Link the current working directory to Herd
  links               Display all of the registered Herd links
  list                List commands
  log                 Tail log file
  logs                Show the log viewer UI for the given site
  loopback            Get or set the loopback address used for Herd sites
  open                Open the site for the current (or specified) directory in your browser
  park                Register the current working (or specified) directory with Herd
  parked              Display all the current sites within parked paths
  paths               Get all of the paths registered with Herd
  php                 Proxy PHP commands with isolated site's PHP executable
  proxies             Display all of the proxy sites
  proxy               Create an Nginx proxy site for the specified host. Useful for Reverb, Docker, etc.
  restart             Restart the Herd services
  secure              Secure the given domain with a trusted TLS certificate
  secured             Display all of the currently secured sites
  share               Share the current site via an Expose tunnel
  site-information    Display information about the specified site
  sites               List all sites
  start               Start the Herd services
  stop                Stop the Herd services
  tinker              Run tinker/Tinkerwell
  tld                 [domain] Get or set the TLD used for Herd sites.
  unisolate           Stop customizing the version of PHP used by Herd to serve the current working directory
  unisolate-node      Stop customizing the version of Node used the CLI for the current working directory
  unlink              Remove the specified Herd link
  unproxy             Delete an Nginx proxy config.
  unsecure            Stop serving the given domain over HTTPS and remove the trusted TLS certificate
  use                 Change the version of PHP used by Herd
  which               Display which Herd driver serves the current working directory
  which-php           Get the PHP executable path for a given site
 debug
  debug:start         Start a debug session (Pro)
  debug:stop          Stop a debug session and display captured data (Pro)
 init
  init:fresh          Initialize a Herd manifest file
 laravel
  laravel:update      Update the Laravel Installer
 php
  php:install         Install a PHP version
  php:list            List all PHP versions and their status
  php:update          Update a PHP version to the latest patch release
 services
  services:available  List all available service types that can be created
  services:clone      Clone an existing service instance
  services:create     Create a new service instance
  services:delete     Delete a service and all its data
  services:list       List all installed services
  services:start      Start a service
  services:stop       Stop a service
  services:versions   List all available versions for a service type


/bin/zsh
-lc
herd list | sed -n '1,220p'

Herd 1.28.0

Usage:
  command [options] [arguments]

Options:
  -h, --help            Display help for the given command. When no command is given display help for the list command
  -q, --quiet           Do not output any message
  -V, --version         Display this application version
      --ansi|--no-ansi  Force (or disable --no-ansi) ANSI output
  -n, --no-interaction  Do not ask any interactive question
  -v|vv|vvv, --verbose  Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug

Available commands:
  completion          Dump the shell completion script
  composer            Proxy Composer commands with isolated site's PHP executable
  coverage            Proxy PHP commands with isolated site's PHP executable and Xdebug code coverage enabled
  db                  Open the database for the current or given site, if credentials are found.
  debug               Proxy PHP commands with isolated site's PHP executable and debug.ini loaded
  directory-listing   Determine directory-listing behavior. Default is off, which means a 404 will display.
  edit                Open the site in your favorite IDE
  fetch-share-url     Get the URL to the current share tunnel for Expose
  forget              [unpark] Remove the current working (or specified) directory from Herd's list of paths
  help                Display help for a command
  ini                 Open the php.ini in your favorite IDE
  init                Start the services defined in the Herd manifest file, and apply the configuration.
  isolate             Change the version of PHP used by Herd to serve the current working directory
  isolate-node        Change the version of Node used by the CLI for the current working directory
  isolated            List all sites using isolated versions of PHP.
  isolated-node       List all sites using isolated versions of Node.
  link                Link the current working directory to Herd
  links               Display all of the registered Herd links
  list                List commands
  log                 Tail log file
  logs                Show the log viewer UI for the given site
  loopback            Get or set the loopback address used for Herd sites
  open                Open the site for the current (or specified) directory in your browser
  park                Register the current working (or specified) directory with Herd
  parked              Display all the current sites within parked paths
  paths               Get all of the paths registered with Herd
  php                 Proxy PHP commands with isolated site's PHP executable
  proxies             Display all of the proxy sites
  proxy               Create an Nginx proxy site for the specified host. Useful for Reverb, Docker, etc.
  restart             Restart the Herd services
  secure              Secure the given domain with a trusted TLS certificate
  secured             Display all of the currently secured sites
  share               Share the current site via an Expose tunnel
  site-information    Display information about the specified site
  sites               List all sites
  start               Start the Herd services
  stop                Stop the Herd services
  tinker              Run tinker/Tinkerwell
  tld                 [domain] Get or set the TLD used for Herd sites.
  unisolate           Stop customizing the version of PHP used by Herd to serve the current working directory
  unisolate-node      Stop customizing the version of Node used the CLI for the current working directory
  unlink              Remove the specified Herd link
  unproxy             Delete an Nginx proxy config.
  unsecure            Stop serving the given domain over HTTPS and remove the trusted TLS certificate
  use                 Change the version of PHP used by Herd
  which               Display which Herd driver serves the current working directory
  which-php           Get the PHP executable path for a given site
 debug
  debug:start         Start a debug session (Pro)
  debug:stop          Stop a debug session and display captured data (Pro)
 init
  init:fresh          Initialize a Herd manifest file
 laravel
  laravel:update      Update the Laravel Installer
 php
  php:install         Install a PHP version
  php:list            List all PHP versions and their status
  php:update          Update a PHP version to the latest patch release
 services
  services:available  List all available service types that can be created
  services:clone      Clone an existing service instance
  services:create     Create a new service instance
  services:delete     Delete a service and all its data
  services:list       List all installed services
  services:start      Start a service
  services:stop       Stop a service
  services:versions   List all available versions for a service type


Output:
Chunk ID: 8ea65d
Wall time: 0.1316 seconds
Process exited with code 0
Original token count: 214
Output:
Description:
  Link the current working directory to Herd

Usage:
  link [options] [--] [<name>]

Arguments:
  name

Options:
      --secure           Link the site with a trusted TLS certificate.
      --isolate=ISOLATE  Isolate the site to the PHP version specified, for example 8.3.
      --update-env       Force update the APP_URL in your .env file.
  -h, --help             Display help for the given command. When no command is given display help for the list command
  -q, --quiet            Do not output any message
  -V, --version          Display this application version
      --ansi|--no-ansi   Force (or disable --no-ansi) ANSI output
  -n, --no-interaction   Do not ask any interactive question
  -v|vv|vvv, --verbose   Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug


Description:
  Link the current working directory to Herd

Usage:
  link [options] [--] [<name>]

Arguments:
  name

Options:
      --secure           Link the site with a trusted TLS certificate.
      --isolate=ISOLATE  Isolate the site to the PHP version specified, for example 8.3.
      --update-env       Force update the APP_URL in your .env file.
  -h, --help             Display help for the given command. When no command is given display help for the list command
  -q, --quiet            Do not output any message
  -V, --version          Display this application version
      --ansi|--no-ansi   Force (or disable --no-ansi) ANSI output
  -n, --no-interaction   Do not ask any interactive question
  -v|vv|vvv, --verbose   Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug


Output:
Chunk ID: 253b4b
Wall time: 1.6865 seconds
Process exited with code 0
Original token count: 60
Output:
A [acme-fashion] symbolic link has been created in [/Users/fabianwesner/Library/Application Support/Herd/config/valet/Sites/acme-fashion].
Laravel Boost detected, updating boost configuration...
Boost configuration updated successfully.


/bin/zsh
-lc
herd link acme-fashion --no-interaction

A [acme-fashion] symbolic link has been created in [/Users/fabianwesner/Library/Application Support/Herd/config/valet/Sites/acme-fashion].
Laravel Boost detected, updating boost configuration...
Boost configuration updated successfully.


Output:
Chunk ID: b5a49a
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 28
Output:
?? .playwright-mcp/console-2026-05-04T05-45-51-349Z.log
?? .playwright-mcp/page-2026-05-04T05-45-51-632Z.yml


Herd linked `acme-fashion.test` to this project without touching tracked repo files. I’m verifying that the seeded domain now resolves through the app rather than the Herd fallback.


### Ran Playwright code
```js
await page.goto('http://acme-fashion.test/');
```
### Open tabs
- 0: [Checkout](http://shop.test/checkout)
- 1: [Dashboard](http://shop.test/admin)
- 2: (current) [Acme Fashion](http://acme-fashion.test/)
### Page
- Page URL: http://acme-fashion.test/
- Page Title: Acme Fashion
### Snapshot
- [Snapshot](.playwright-mcp/page-2026-05-04T05-46-53-892Z.yml)
### Events
- New console entries: .playwright-mcp/console-2026-05-04T05-46-53-506Z.log#L1


Output:
Wall time: 0.3884 seconds
Output:
[{"type":"text","text":"### Ran Playwright code\n```js\nawait page.goto('http://acme-fashion.test/');\n```\n### Open tabs\n- 0: [Checkout](http://shop.test/checkout)\n- 1: [Dashboard](http://shop.test/admin)\n- 2: (current) [Acme Fashion](http://acme-fashion.test/)\n### Page\n- Page URL: http://acme-fashion.test/\n- Page Title: Acme Fashion\n### Snapshot\n- [Snapshot](.playwright-mcp/page-2026-05-04T05-46-53-892Z.yml)\n### Events\n- New console entries: .playwright-mcp/console-2026-05-04T05-46-53-506Z.log#L1"}]


### Result
Total messages: 3 (Errors: 0, Warnings: 0)
Returning 0 messages for level "warning"


Output:
Wall time: 0.0180 seconds
Output:
[{"type":"text","text":"### Result\nTotal messages: 3 (Errors: 0, Warnings: 0)\nReturning 0 messages for level \"warning\"\n"}]


[2026-05-03 21:32:56] local.ERROR: Uncaught ReferenceError: fluxInputViewable is not defined http://shop.test/livewire-6701cc17/livewire.js?id=cfc5c1ae 1330 7 ReferenceError fluxInputViewable is not defined ReferenceError: fluxInputViewable is not defined
    at [Alpine] fluxInputViewable (eval at safeAsyncFunction (http://shop.test/livewire-6701cc17/livewire.js?id=cfc5c1ae:1385:21), <anonymous>:3:32)
    at http://shop.test/livewire-6701cc17/livewire.js?id=cfc5c1ae:1410:28
    at tryCatch (http://shop.test/livewire-6701cc17/livewire.js?id=cfc5c1ae:1309:14)
    at evaluate (http://shop.test/livewire-6701cc17/livewire.js?id=cfc5c1ae:1343:34)
    at Function.<anonymous> (http://shop.test/livewire-6701cc17/livewire.js?id=cfc5c1ae:3890:17)
    at flushHandlers (http://shop.test/livewire-6701cc17/livewire.js?id=cfc5c1ae:1527:48)
    at stopDeferring (http://shop.test/livewire-6701cc17/livewire.js?id=cfc5c1ae:1532:7)
    at deferHandlingDirectives (http://shop.test/livewire-6701cc17/livewire.js?id=cfc5c1ae:1535:5)
    at initTree (http://shop.test/livewire-6701cc17/livewire.js?id=cfc5c1ae:1732:5)
    at http://shop.test/livewire-6701cc17/livewire.js?id=cfc5c1ae:1678:7 {"url":"http://shop.test/account/login","user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/147.0.0.0 Safari/537.36","timestamp":"2026-05-03T21:32:55.973Z"}

[2026-05-03 21:32:56] local.ERROR: Uncaught ReferenceError: fluxInputViewable is not defined http://shop.test/livewire-6701cc17/livewire.js?id=cfc5c1ae 1330 7 ReferenceError fluxInputViewable is not defined ReferenceError: fluxInputViewable is not defined
    at [Alpine] fluxInputViewable (eval at safeAsyncFunction (http://shop.test/livewire-6701cc17/livewire.js?id=cfc5c1ae:1385:21), <anonymous>:3:32)
    at http://shop.test/livewire-6701cc17/livewire.js?id=cfc5c1ae:1410:28
    at tryCatch (http://shop.test/livewire-6701cc17/livewire.js?id=cfc5c1ae:1309:14)
    at evaluate (http://shop.test/livewire-6701cc17/livewire.js?id=cfc5c1ae:1343:34)
    at Function.<anonymous> (http://shop.test/livewire-6701cc17/livewire.js?id=cfc5c1ae:3890:17)
    at flushHandlers (http://shop.test/livewire-6701cc17/livewire.js?id=cfc5c1ae:1527:48)
    at stopDeferring (http://shop.test/livewire-6701cc17/livewire.js?id=cfc5c1ae:1532:7)
    at deferHandlingDirectives (http://shop.test/livewire-6701cc17/livewire.js?id=cfc5c1ae:1535:5)
    at initTree (http://shop.test/livewire-6701cc17/livewire.js?id=cfc5c1ae:1732:5)
    at http://shop.test/livewire-6701cc17/livewire.js?id=cfc5c1ae:1678:7 {"url":"http://shop.test/account/login","user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/147.0.0.0 Safari/537.36","timestamp":"2026-05-03T21:32:55.973Z"}

[2026-05-03 21:32:56] local.ERROR: Uncaught ReferenceError: fluxInputViewable is not defined http://shop.test/livewire-6701cc17/livewire.js?id=cfc5c1ae 1330 7 ReferenceError fluxInputViewable is not defined ReferenceError: fluxInputViewable is not defined
    at [Alpine] fluxInputViewable (eval at safeAsyncFunction (http://shop.test/livewire-6701cc17/livewire.js?id=cfc5c1ae:1385:21), <anonymous>:3:32)
    at http://shop.test/livewire-6701cc17/livewire.js?id=cfc5c1ae:1410:28
    at tryCatch (http://shop.test/livewire-6701cc17/livewire.js?id=cfc5c1ae:1309:14)
    at evaluate (http://shop.test/livewire-6701cc17/livewire.js?id=cfc5c1ae:1343:34)
    at Function.<anonymous> (http://shop.test/livewire-6701cc17/livewire.js?id=cfc5c1ae:3890:17)
    at flushHandlers (http://shop.test/livewire-6701cc17/livewire.js?id=cfc5c1ae:1527:48)
    at stopDeferring (http://shop.test/livewire-6701cc17/livewire.js?id=cfc5c1ae:1532:7)
    at deferHandlingDirectives (http://shop.test/livewire-6701cc17/livewire.js?id=cfc5c1ae:1535:5)
    at initTree (http://shop.test/livewire-6701cc17/livewire.js?id=cfc5c1ae:1732:5)
    at http://shop.test/livewire-6701cc17/livewire.js?id=cfc5c1ae:1678:7 {"url":"http://shop.test/account/login","user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/147.0.0.0 Safari/537.36","timestamp":"2026-05-03T21:32:55.973Z"}

[2026-05-03 21:32:56] local.WARNING: Livewire: missing closing tags found. Ensure your template elements contain matching closing tags. Y1jQ8DDcYzgTQ9o78LDp storefront.account.auth.login null null null false Y1jQ8DDcYzgTQ9o78LDp storefront.account.auth.login null null null false null null null 1 null {"url":"http://shop.test/account/login","user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/147.0.0.0 Safari/537.36","timestamp":"2026-05-03T21:32:55.973Z"}

[2026-05-03 21:32:56] local.WARNING: Livewire: missing closing tags found. Ensure your template elements contain matching closing tags. Y1jQ8DDcYzgTQ9o78LDp storefront.account.auth.login null null null false Y1jQ8DDcYzgTQ9o78LDp storefront.account.auth.login null null null false null null null 1 null {"url":"http://shop.test/account/login","user_agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/147.0.0.0 Safari/537.36","timestamp":"2026-05-03T21:32:55.973Z"}


Output:
Wall time: 0.1927 seconds
Output:
[{"type":"text","text":"[2026-05-03 21:32:56] local.ERROR: Uncaught ReferenceError: fluxInputViewable is not defined http://shop.test/livewire-6701cc17/livewire.js?id=cfc5c1ae 1330 7 ReferenceError fluxInputViewable is not defined ReferenceError: fluxInputViewable is not defined\n    at [Alpine] fluxInputViewable (eval at safeAsyncFunction (http://shop.test/livewire-6701cc17/livewire.js?id=cfc5c1ae:1385:21), <anonymous>:3:32)\n    at http://shop.test/livewire-6701cc17/livewire.js?id=cfc5c1ae:1410:28\n    at tryCatch (http://shop.test/livewire-6701cc17/livewire.js?id=cfc5c1ae:1309:14)\n    at evaluate (http://shop.test/livewire-6701cc17/livewire.js?id=cfc5c1ae:1343:34)\n    at Function.<anonymous> (http://shop.test/livewire-6701cc17/livewire.js?id=cfc5c1ae:3890:17)\n    at flushHandlers (http://shop.test/livewire-6701cc17/livewire.js?id=cfc5c1ae:1527:48)\n    at stopDeferring (http://shop.test/livewire-6701cc17/livewire.js?id=cfc5c1ae:1532:7)\n    at deferHandlingDirectives (http://shop.test/livewire-6701cc17/livewire.js?id=cfc5c1ae:1535:5)\n    at initTree (http://shop.test/livewire-6701cc17/livewire.js?id=cfc5c1ae:1732:5)\n    at http://shop.test/livewire-6701cc17/livewire.js?id=cfc5c1ae:1678:7 {\"url\":\"http://shop.test/account/login\",\"user_agent\":\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/147.0.0.0 Safari/537.36\",\"timestamp\":\"2026-05-03T21:32:55.973Z\"} \n\n\n[2026-05-03 21:32:56] local.ERROR: Uncaught ReferenceError: fluxInputViewable is not defined http://shop.test/livewire-6701cc17/livewire.js?id=cfc5c1ae 1330 7 ReferenceError fluxInputViewable is not defined ReferenceError: fluxInputViewable is not defined\n    at [Alpine] fluxInputViewable (eval at safeAsyncFunction (http://shop.test/livewire-6701cc17/livewire.js?id=cfc5c1ae:1385:21), <anonymous>:3:32)\n    at http://shop.test/livewire-6701cc17/livewire.js?id=cfc5c1ae:1410:28\n    at tryCatch (http://shop.test/livewire-6701cc17/livewire.js?id=cfc5c1ae:1309:14)\n    at evaluate (http://shop.test/livewire-6701cc17/livewire.js?id=cfc5c1ae:1343:34)\n    at Function.<anonymous> (http://shop.test/livewire-6701cc17/livewire.js?id=cfc5c1ae:3890:17)\n    at flushHandlers (http://shop.test/livewire-6701cc17/livewire.js?id=cfc5c1ae:1527:48)\n    at stopDeferring (http://shop.test/livewire-6701cc17/livewire.js?id=cfc5c1ae:1532:7)\n    at deferHandlingDirectives (http://shop.test/livewire-6701cc17/livewire.js?id=cfc5c1ae:1535:5)\n    at initTree (http://shop.test/livewire-6701cc17/livewire.js?id=cfc5c1ae:1732:5)\n    at http://shop.test/livewire-6701cc17/livewire.js?id=cfc5c1ae:1678:7 {\"url\":\"http://shop.test/account/login\",\"user_agent\":\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/147.0.0.0 Safari/537.36\",\"timestamp\":\"2026-05-03T21:32:55.973Z\"} \n\n\n[2026-05-03 21:32:56] local.ERROR: Uncaught ReferenceError: fluxInputViewable is not defined http://shop.test/livewire-6701cc17/livewire.js?id=cfc5c1ae 1330 7 ReferenceError fluxInputViewable is not defined ReferenceError: fluxInputViewable is not defined\n    at [Alpine] fluxInputViewable (eval at safeAsyncFunction (http://shop.test/livewire-6701cc17/livewire.js?id=cfc5c1ae:1385:21), <anonymous>:3:32)\n    at http://shop.test/livewire-6701cc17/livewire.js?id=cfc5c1ae:1410:28\n    at tryCatch (http://shop.test/livewire-6701cc17/livewire.js?id=cfc5c1ae:1309:14)\n    at evaluate (http://shop.test/livewire-6701cc17/livewire.js?id=cfc5c1ae:1343:34)\n    at Function.<anonymous> (http://shop.test/livewire-6701cc17/livewire.js?id=cfc5c1ae:3890:17)\n    at flushHandlers (http://shop.test/livewire-6701cc17/livewire.js?id=cfc5c1ae:1527:48)\n    at stopDeferring (http://shop.test/livewire-6701cc17/livewire.js?id=cfc5c1ae:1532:7)\n    at deferHandlingDirectives (http://shop.test/livewire-6701cc17/livewire.js?id=cfc5c1ae:1535:5)\n    at initTree (http://shop.test/livewire-6701cc17/livewire.js?id=cfc5c1ae:1732:5)\n    at http://shop.test/livewire-6701cc17/livewire.js?id=cfc5c1ae:1678:7 {\"url\":\"http://shop.test/account/login\",\"user_agent\":\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/147.0.0.0 Safari/537.36\",\"timestamp\":\"2026-05-03T21:32:55.973Z\"} \n\n\n[2026-05-03 21:32:56] local.WARNING: Livewire: missing closing tags found. Ensure your template elements contain matching closing tags. Y1jQ8DDcYzgTQ9o78LDp storefront.account.auth.login null null null false Y1jQ8DDcYzgTQ9o78LDp storefront.account.auth.login null null null false null null null 1 null {\"url\":\"http://shop.test/account/login\",\"user_agent\":\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/147.0.0.0 Safari/537.36\",\"timestamp\":\"2026-05-03T21:32:55.973Z\"} \n\n\n[2026-05-03 21:32:56] local.WARNING: Livewire: missing closing tags found. Ensure your template elements contain matching closing tags. Y1jQ8DDcYzgTQ9o78LDp storefront.account.auth.login null null null false Y1jQ8DDcYzgTQ9o78LDp storefront.account.auth.login null null null false null null null 1 null {\"url\":\"http://shop.test/account/login\",\"user_agent\":\"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/147.0.0.0 Safari/537.36\",\"timestamp\":\"2026-05-03T21:32:55.973Z\"} \n"}]


`acme-fashion.test` now renders the seeded Acme Fashion storefront, and the current Playwright console check is clean. I’m recording that Herd alias verification and removing the domain open issue.


Input:
*** Begin Patch
*** Update File: /Users/fabianwesner/Herd/shop/specs/progress.md
@@
-| Browser E2E plan | `specs/08-PLAYWRIGHT-E2E-PLAN.md` | partial | Manual Playwright MCP smoke coverage has verified storefront catalog browsing, 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 navigation, 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 navigation 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 browser tests are 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 navigation, 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 navigation 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 browser tests are still missing. |
@@
 - 2026-05-04: `php artisan route:list --path=api/admin/v1 --except-vendor` confirmed the four admin order API routes remain registered.
 - 2026-05-04: `php artisan test --compact` passed after the admin API token middleware changes: 204 tests, 1083 assertions.
 - 2026-05-04: `php artisan migrate:fresh --seed --no-interaction` passed after the admin API token middleware changes.
+- 2026-05-04: Playwright MCP initially confirmed `http://acme-fashion.test/` reached Herd's site-not-found page, then `herd sites | rg "shop|acme"` confirmed only `shop.test` was linked for the project.
+- 2026-05-04: `herd link acme-fashion --no-interaction` linked the current project to `http://acme-fashion.test` for the E2E spec host without changing tracked repo files.
+- 2026-05-04: Playwright MCP verified `http://acme-fashion.test/` renders the seeded Acme Fashion storefront after the Herd link; current Playwright console checks reported no warnings/errors. Boost `browser_logs` only returned older May 3 account-login warnings from a previously fixed issue.
@@
 - Seed both `shop.test` and `acme-fashion.test` as storefront domains for the first store so the goal URL and E2E spec URL can both resolve.
+- Herd needs a site link for non-project-folder aliases; `acme-fashion.test` is linked to the same local project path as `shop.test` so the seeded domain can be verified in browser E2E flows.
@@
-- The Herd app URL in the goal is `http://shop.test/`, while the E2E spec also references `http://acme-fashion.test`; domain handling must support seeded store domains and Herd verification.
 - Customer password reset is implemented under `/account/forgot-password` and `/account/reset-password/{token}`; the exact spec root paths remain occupied by the existing Fortify starter/admin reset routes.
*** End Patch


Success. Updated the following files:
M /Users/fabianwesner/Herd/shop/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, 17 navigation items, 2 tax settings rows, 2 shipping zones, 6 shipping rates, 6 discounts, 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, 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 navigation, 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 navigation 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 browser tests are 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 navigation, 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 navigation 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 browser tests are still missing. |
 | 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. |
@@ -307,2 +307,5 @@
 - 2026-05-04: `php artisan migrate:fresh --seed --no-interaction` passed after the admin API token middleware changes.
+- 2026-05-04: Playwright MCP initially confirmed `http://acme-fashion.test/` reached Herd's site-not-found page, then `herd sites | rg "shop|acme"` confirmed only `shop.test` was linked for the project.
+- 2026-05-04: `herd link acme-fashion --no-interaction` linked the current project to `http://acme-fashion.test` for the E2E spec host without changing tracked repo files.
+- 2026-05-04: Playwright MCP verified `http://acme-fashion.test/` renders the seeded Acme Fashion storefront after the Herd link; current Playwright console checks reported no warnings/errors. Boost `browser_logs` only returned older May 3 account-login warnings from a previously fixed issue.

@@ -315,2 +318,3 @@
 - Seed both `shop.test` and `acme-fashion.test` as storefront domains for the first store so the goal URL and E2E spec URL can both resolve.
+- Herd needs a site link for non-project-folder aliases; `acme-fashion.test` is linked to the same local project path as `shop.test` so the seeded domain can be verified in browser E2E flows.
 - Customer Livewire auth components persist `storeId` from the initial storefront request because Livewire update requests do not run through the original storefront route middleware.
@@ -368,3 +372,2 @@

-- The Herd app URL in the goal is `http://shop.test/`, while the E2E spec also references `http://acme-fashion.test`; domain handling must support seeded store domains and Herd verification.
 - Customer password reset is implemented under `/account/forgot-password` and `/account/reset-password/{token}`; the exact spec root paths remain occupied by the existing Fortify starter/admin reset routes.


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


git clean -f .playwright-mcp && git diff --check && git status --short


git clean -f .playwright-mcp && git diff --check && git status --short

Removing .playwright-mcp/console-2026-05-04T05-45-51-349Z.log
Removing .playwright-mcp/console-2026-05-04T05-46-53-506Z.log
Removing .playwright-mcp/page-2026-05-04T05-45-51-632Z.yml
Removing .playwright-mcp/page-2026-05-04T05-46-53-892Z.yml
 M specs/progress.md


Output:
Chunk ID: c6d4b2
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 66
Output:
Removing .playwright-mcp/console-2026-05-04T05-45-51-349Z.log
Removing .playwright-mcp/console-2026-05-04T05-46-53-506Z.log
Removing .playwright-mcp/page-2026-05-04T05-45-51-632Z.yml
Removing .playwright-mcp/page-2026-05-04T05-46-53-892Z.yml
 M specs/progress.md


git add specs/progress.md && git commit -m "Record acme fashion Herd verification"


git add specs/progress.md && git commit -m "Record acme fashion Herd verification"

[2026-05-04-codex-5-5-goal f77e9311] Record acme fashion Herd verification
 1 file changed, 5 insertions(+), 2 deletions(-)


Output:
Chunk ID: 1557dd
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 31
Output:
[2026-05-04-codex-5-5-goal f77e9311] Record acme fashion Herd verification
 1 file changed, 5 insertions(+), 2 deletions(-)


Committed `f77e9311 Record acme fashion Herd verification`. The next remaining app feature gap is navigation nesting; this is larger because it touches schema, service output, admin UI, storefront rendering, seed data, and tests. I’m going to inspect the current navigation model and spec before making the migration.


sed -n '1,180p' /Users/fabianwesner/Herd/shop/.agents/skills/livewire-development/SKILL.md && sed -n '1,160p' /Users/fabianwesner/Herd/shop/.agents/skills/fluxui-development/SKILL.md && sed -n '1,180p' /Users/fabianwesner/Herd/shop/.agents/skills/tailwindcss-development/SKILL.md


sed -n '1,180p' /Users/fabianwesner/Herd/shop/.agents/skills/livewire-development/SKILL.md && sed -n '1,160p' /Users/fabianwesner/Herd/shop/.agents/skills/fluxui-development/SKILL.md && sed -n '1,180p' /Users/fabianwesner/Herd/shop/.agents/skills/tailwindcss-development/SKILL.md

SKILL.md

---
name: livewire-development
description: "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."
license: MIT
metadata:
  author: laravel
---

# Livewire Development

## Documentation

Use `search-docs` for detailed Livewire 4 patterns and documentation.

## Basic Usage

### Creating Components

```bash

# Single-file component (SFC - default in v4)

# Creates: resources/views/components/⚡create-post.blade.php

php artisan make:livewire create-post

# Page component (SFC - Full Page in v4)

# Creates: resources/views/pages/⚡create-post.blade.php

php artisan make:livewire pages::create-post

# Multi-file component (MFC)

# Creates: resources/views/components/⚡create-post/create-post.php

#          resources/views/components/⚡create-post/create-post.blade.php

php artisan make:livewire create-post --mfc

# Class-based component (v3 style)

# Creates: app/Livewire/CreatePost.php AND resources/views/livewire/create-post.blade.php

php artisan make:livewire create-post --class

# With namespace

php artisan make:livewire Posts/CreatePost
```

### Converting Between Formats

Use `php artisan livewire:convert create-post` to convert between single-file, multi-file, and class-based formats.

### Choosing a Component Format

> **Always follow the project's existing conventions first.** Before creating any component, inspect the project's existing Livewire components to determine the established format (SFC, MFC, or class-based) and directory structure. Check `app/Livewire/`, `resources/views/components/`, and `resources/views/livewire/` for existing components. If the project already uses a consistent format, **use that same format** — even if it differs from the Livewire v4 defaults below. Only fall back to the v4 defaults (SFC in `resources/views/components/`) when no existing convention is established.

Also check `config/livewire.php` for `make_command.type`, `make_command.emoji`, `component_locations`, and `component_namespaces` overrides, which change the default format and where files are stored.

### Component Format Reference

| Format | Flag | Class Path | View Path |
|--------|------|------------|-----------|
| Single-file (SFC) | default | — | `resources/views/components/⚡create-post.blade.php` (PHP + Blade in one file) |
| Full Page SFC | `pages::name` | — | `resources/views/pages/⚡create-post.blade.php` |
| Multi-file (MFC) | `--mfc` | `resources/views/components/⚡create-post/create-post.php` | `resources/views/components/⚡create-post/create-post.blade.php` |
| Class-based | `--class` | `app/Livewire/CreatePost.php` | `resources/views/livewire/create-post.blade.php` |
| View-based | default (Blade-only) | — | `resources/views/components/⚡create-post.blade.php` (Blade-only with functional state) |

> **Important:** The ⚡ prefix shown above is the **default** behavior in Livewire v4 — it is **configurable**. Check `config/livewire.php` for the `make_command.emoji` setting. When `true` (default), always include the ⚡ prefix in filenames you create. When `false`, omit the ⚡ prefix from all paths above.

Namespaced components map to subdirectories: `make:livewire Posts/CreatePost` creates `resources/views/components/posts/⚡create-post.blade.php` (single-file by default). Use `make:livewire Posts/CreatePost --mfc` for multi-file output at `resources/views/components/posts/⚡create-post/create-post.php` and `resources/views/components/posts/⚡create-post/create-post.blade.php`.

### Single-File Component Example

<!-- Single-File Component Example -->
```php
<?php
use Livewire\Component;

new class extends Component {
    public int $count = 0;

    public function increment(): void
    {
        $this->count++;
    }
};
?>

<div>
    <button wire:click="increment">Count: @{{ $count }}</button>
</div>
```

## Livewire 4 Specifics

### Key Changes From Livewire 3

These things changed in Livewire 4, but may not have been updated in this application. Verify this application's setup to ensure you follow existing conventions.

- Use `Route::livewire()` for full-page components (e.g., `Route::livewire('/posts/create', CreatePost::class)`); config keys renamed: `layout` → `component_layout`, `lazy_placeholder` → `component_placeholder`.
- `wire:model` now ignores child events by default (use `wire:model.deep` for old behavior); `wire:scroll` renamed to `wire:navigate:scroll`.
- Component tags must be properly closed; `wire:transition` now uses View Transitions API (modifier…4087 chars truncated…lt icon set. Search for exact icon names on the Heroicons site - do not guess or invent icon names.

<!-- Icon Button -->
```blade
<flux:button icon="arrow-down-tray">Export</flux:button>
```

For icons not available in Heroicons, use [Lucide](https://lucide.dev/). Import the icons you need with the Artisan command:

```bash
php artisan flux:icon crown grip-vertical github
```

## Common Patterns

### Form Fields

<!-- Form Field -->
```blade
<flux:field>
    <flux:label>Email</flux:label>
    <flux:input type="email" wire:model="email" />
    <flux:error name="email" />
</flux:field>
```

### Modals

<!-- Modal -->
```blade
<flux:modal wire:model="showModal">
    <flux:heading>Title</flux:heading>
    <p>Content</p>
</flux:modal>
```

## Verification

1. Check component renders correctly
2. Test interactive states
3. Verify mobile responsiveness

## Common Pitfalls

- Trying to use Pro-only components in the free edition
- Not checking if a Flux component exists before creating custom implementations
- Forgetting to use the `search-docs` tool for component-specific documentation
- Not following existing project patterns for Flux usage---
name: tailwindcss-development
description: "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."
license: MIT
metadata:
  author: laravel
---

# Tailwind CSS Development

## Documentation

Use `search-docs` for detailed Tailwind CSS v4 patterns and documentation.

## Basic Usage

- Use Tailwind CSS classes to style HTML. Check and follow existing Tailwind conventions in the project before introducing new patterns.
- Offer to extract repeated patterns into components that match the project's conventions (e.g., Blade, JSX, Vue).
- Consider class placement, order, priority, and defaults. Remove redundant classes, add classes to parent or child elements carefully to reduce repetition, and group elements logically.

## Tailwind CSS v4 Specifics

- Always use Tailwind CSS v4 and avoid deprecated utilities.
- `corePlugins` is not supported in Tailwind v4.

### CSS-First Configuration

In Tailwind v4, configuration is CSS-first using the `@theme` directive — no separate `tailwind.config.js` file is needed:

<!-- CSS-First Config -->
```css
@theme {
  --color-brand: oklch(0.72 0.11 178);
}
```

### Import Syntax

In Tailwind v4, import Tailwind with a regular CSS `@import` statement instead of the `@tailwind` directives used in v3:

<!-- v4 Import Syntax -->
```diff
- @tailwind base;
- @tailwind components;
- @tailwind utilities;
+ @import "tailwindcss";
```

### Replaced Utilities

Tailwind v4 removed deprecated utilities. Use the replacements shown below. Opacity values remain numeric.

| Deprecated | Replacement |
|------------|-------------|
| bg-opacity-* | bg-black/* |
| text-opacity-* | text-black/* |
| border-opacity-* | border-black/* |
| divide-opacity-* | divide-black/* |
| ring-opacity-* | ring-black/* |
| placeholder-opacity-* | placeholder-black/* |
| flex-shrink-* | shrink-* |
| flex-grow-* | grow-* |
| overflow-ellipsis | text-ellipsis |
| decoration-slice | box-decoration-slice |
| decoration-clone | box-decoration-clone |

## Spacing

Use `gap` utilities instead of margins for spacing between siblings:

<!-- Gap Utilities -->
```html
<div class="flex gap-8">
    <div>Item 1</div>
    <div>Item 2</div>
</div>
```

## Dark Mode

If existing pages and components support dark mode, new pages and components must support it the same way, typically using the `dark:` variant:

<!-- Dark Mode -->
```html
<div class="bg-white dark:bg-gray-900 text-gray-900 dark:text-white">
    Content adapts to color scheme
</div>
```

## Common Patterns

### Flexbox Layout

<!-- Flexbox Layout -->
```html
<div class="flex items-center justify-between gap-4">
    <div>Left content</div>
    <div>Right content</div>
</div>
```

### Grid Layout

<!-- Grid Layout -->
```html
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
    <div>Card 1</div>
    <div>Card 2</div>
    <div>Card 3</div>
</div>
```

## Common Pitfalls

- Using deprecated v3 utilities (bg-opacity-*, flex-shrink-*, etc.)
- Using `@tailwind` directives instead of `@import "tailwindcss"`
- Trying to use `tailwind.config.js` instead of CSS `@theme` directive
- Using margins for spacing between siblings instead of gap utilities
- Forgetting to add dark mode variants when the project uses dark mode


Output:
Chunk ID: a6f65b
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 3525
Output:
---
name: livewire-development
description: "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."
license: MIT
metadata:
  author: laravel
---

# Livewire Development

## Documentation

Use `search-docs` for detailed Livewire 4 patterns and documentation.

## Basic Usage

### Creating Components

```bash

# Single-file component (SFC - default in v4)

# Creates: resources/views/components/⚡create-post.blade.php

php artisan make:livewire create-post

# Page component (SFC - Full Page in v4)

# Creates: resources/views/pages/⚡create-post.blade.php

php artisan make:livewire pages::create-post

# Multi-file component (MFC)

# Creates: resources/views/components/⚡create-post/create-post.php

#          resources/views/components/⚡create-post/create-post.blade.php

php artisan make:livewire create-post --mfc

# Class-based component (v3 style)

# Creates: app/Livewire/CreatePost.php AND resources/views/livewire/create-post.blade.php

php artisan make:livewire create-post --class

# With namespace

php artisan make:livewire Posts/CreatePost
```

### Converting Between Formats

Use `php artisan livewire:convert create-post` to convert between single-file, multi-file, and class-based formats.

### Choosing a Component Format

> **Always follow the project's existing conventions first.** Before creating any component, inspect the project's existing Livewire components to determine the established format (SFC, MFC, or class-based) and directory structure. Check `app/Livewire/`, `resources/views/components/`, and `resources/views/livewire/` for existing components. If the project already uses a consistent format, **use that same format** — even if it differs from the Livewire v4 defaults below. Only fall back to the v4 defaults (SFC in `resources/views/components/`) when no existing convention is established.

Also check `config/livewire.php` for `make_command.type`, `make_command.emoji`, `component_locations`, and `component_namespaces` overrides, which change the default format and where files are stored.

### Component Format Reference

| Format | Flag | Class Path | View Path |
|--------|------|------------|-----------|
| Single-file (SFC) | default | — | `resources/views/components/⚡create-post.blade.php` (PHP + Blade in one file) |
| Full Page SFC | `pages::name` | — | `resources/views/pages/⚡create-post.blade.php` |
| Multi-file (MFC) | `--mfc` | `resources/views/components/⚡create-post/create-post.php` | `resources/views/components/⚡create-post/create-post.blade.php` |
| Class-based | `--class` | `app/Livewire/CreatePost.php` | `resources/views/livewire/create-post.blade.php` |
| View-based | default (Blade-only) | — | `resources/views/components/⚡create-post.blade.php` (Blade-only with functional state) |

> **Important:** The ⚡ prefix shown above is the **default** behavior in Livewire v4 — it is **configurable**. Check `config/livewire.php` for the `make_command.emoji` setting. When `true` (default), always include the ⚡ prefix in filenames you create. When `false`, omit the ⚡ prefix from all paths above.

Namespaced components map to subdirectories: `make:livewire Posts/CreatePost` creates `resources/views/components/posts/⚡create-post.blade.php` (single-file by default). Use `make:livewire Posts/CreatePost --mfc` for multi-file output at `resources/views/components/posts/⚡create-post/create-post.php` and `resources/views/components/posts/⚡create-post/create-post.blade.php`.

### Single-File Component Example

<!-- Single-File Component Example -->
```php
<?php
use Livewire\Component;

new class extends Component {
    public int $count = 0;

    public function increment(): void
    {
        $this->count++;
    }
};
?>

<div>
    <button wire:click="increment">Count: @{{ $count }}</button>
</div>
```

## Livewire 4 Specifics

### Key Changes From Livewire 3

These things changed in Livewire 4, but may not have been updated in this application. Verify this application's setup to ensure you follow existing conventions.

- Use `Route::livewire()` for full-page components (e.g., `Route::livewire('/posts/create', CreatePost::class)`); config keys renamed: `layout` → `component_layout`, `lazy_placeholder` → `component_placeholder`.
- `wire:model` now ignores child events by default (use `wire:model.deep` for old behavior); `wire:scroll` renamed to `wire:navigate:scroll`.
- Component tags must be properly closed; `wire:transition` now uses View Transitions API (modifiers removed).
- JavaScript: `$wire.$js('name', fn)` → `$wire.$js.name = fn`; `commit`/`request` hooks → `interceptMessage()`/`interceptRequest()`.

### New Features

- Component formats: single-file (SFC), multi-file (MFC), view-based components.
- Islands (`@island`) for isolated updates; async actions (`wire:click.async`, `#[Async]`) for parallel execution.
- Deferred/bundled loading: `defer`, `lazy.bundle` for optimized component loading.

| Feature | Usage | Purpose |
|---------|-------|---------|
| Islands | `@island(name: 'stats')` | Isolated update regions |
| Async | `wire:click.async` or `#[Async]` | Non-blocking actions |
| Deferred | `defer` attribute | Load after page render |
| Bundled | `lazy.bundle` | Load multiple together |

### New Directives

- `wire:sort`, `wire:intersect`, `wire:ref`, `.renderless`, `.preserve-scroll` are available for use.
- `data-loading` attribute automatically added to elements triggering network requests.

| Directive | Purpose |
|-----------|---------|
| `wire:sort` | Drag-and-drop sorting |
| `wire:intersect` | Viewport intersection detection |
| `wire:ref` | Element references for JS |
| `.renderless` | Component without rendering |
| `.preserve-scroll` | Preserve scroll position |

## Best Practices

- Always use `wire:key` in loops
- Use `wire:loading` for loading states
- Use `wire:model.live` for instant updates (default is debounced)
- Validate and authorize in actions (treat like HTTP requests)

## Configuration

- `smart_wire_keys` defaults to `true`; new configs: `component_locations`, `component_namespaces`, `make_command`, `csp_safe`.

## Alpine & JavaScript

- `wire:transition` uses browser View Transitions API; `$errors` and `$intercept` magic properties available.
- Non-blocking `wire:poll` and parallel `wire:model.live` updates improve performance.

For interceptors and hooks, see [reference/javascript-hooks.md](reference/javascript-hooks.md).

## Testing

<!-- Testing Example -->
```php
Livewire::test(Counter::class)
    ->assertSet('count', 0)
    ->call('increment')
    ->assertSet('count', 1);
```

## Verification

1. Browser console: Check for JS errors
2. Network tab: Verify Livewire requests return 200
3. Ensure `wire:key` on all `@foreach` loops

## Common Pitfalls

- Missing `wire:key` in loops → unexpected re-rendering
- Expecting `wire:model` real-time → use `wire:model.live`
- Unclosed component tags → syntax errors in v4
- Using deprecated config keys or JS hooks
- Including Alpine.js separately (already bundled in Livewire 4)---
name: fluxui-development
description: "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."
license: MIT
metadata:
  author: laravel
---

# Flux UI Development

## Documentation

Use `search-docs` for detailed Flux UI patterns and documentation.

## Basic Usage

This project uses the free edition of Flux UI, which includes all free components and variants but not Pro components.

Flux UI is a component library for Livewire built with Tailwind CSS. It provides components that are easy to use and customize.

Use Flux UI components when available. Fall back to standard Blade components when no Flux component exists for your needs.

<!-- Basic Button -->
```blade
<flux:button variant="primary">Click me</flux:button>
```

## Available Components (Free Edition)

Available: avatar, badge, brand, breadcrumbs, button, callout, checkbox, dropdown, field, heading, icon, input, modal, navbar, otp-input, profile, radio, select, separator, skeleton, switch, text, textarea, tooltip

## Icons

Flux includes [Heroicons](https://heroicons.com/) as its default icon set. Search for exact icon names on the Heroicons site - do not guess or invent icon names.

<!-- Icon Button -->
```blade
<flux:button icon="arrow-down-tray">Export</flux:button>
```

For icons not available in Heroicons, use [Lucide](https://lucide.dev/). Import the icons you need with the Artisan command:

```bash
php artisan flux:icon crown grip-vertical github
```

## Common Patterns

### Form Fields

<!-- Form Field -->
```blade
<flux:field>
    <flux:label>Email</flux:label>
    <flux:input type="email" wire:model="email" />
    <flux:error name="email" />
</flux:field>
```

### Modals

<!-- Modal -->
```blade
<flux:modal wire:model="showModal">
    <flux:heading>Title</flux:heading>
    <p>Content</p>
</flux:modal>
```

## Verification

1. Check component renders correctly
2. Test interactive states
3. Verify mobile responsiveness

## Common Pitfalls

- Trying to use Pro-only components in the free edition
- Not checking if a Flux component exists before creating custom implementations
- Forgetting to use the `search-docs` tool for component-specific documentation
- Not following existing project patterns for Flux usage---
name: tailwindcss-development
description: "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."
license: MIT
metadata:
  author: laravel
---

# Tailwind CSS Development

## Documentation

Use `search-docs` for detailed Tailwind CSS v4 patterns and documentation.

## Basic Usage

- Use Tailwind CSS classes to style HTML. Check and follow existing Tailwind conventions in the project before introducing new patterns.
- Offer to extract repeated patterns into components that match the project's conventions (e.g., Blade, JSX, Vue).
- Consider class placement, order, priority, and defaults. Remove redundant classes, add classes to parent or child elements carefully to reduce repetition, and group elements logically.

## Tailwind CSS v4 Specifics

- Always use Tailwind CSS v4 and avoid deprecated utilities.
- `corePlugins` is not supported in Tailwind v4.

### CSS-First Configuration

In Tailwind v4, configuration is CSS-first using the `@theme` directive — no separate `tailwind.config.js` file is needed:

<!-- CSS-First Config -->
```css
@theme {
  --color-brand: oklch(0.72 0.11 178);
}
```

### Import Syntax

In Tailwind v4, import Tailwind with a regular CSS `@import` statement instead of the `@tailwind` directives used in v3:

<!-- v4 Import Syntax -->
```diff
- @tailwind base;
- @tailwind components;
- @tailwind utilities;
+ @import "tailwindcss";
```

### Replaced Utilities

Tailwind v4 removed deprecated utilities. Use the replacements shown below. Opacity values remain numeric.

| Deprecated | Replacement |
|------------|-------------|
| bg-opacity-* | bg-black/* |
| text-opacity-* | text-black/* |
| border-opacity-* | border-black/* |
| divide-opacity-* | divide-black/* |
| ring-opacity-* | ring-black/* |
| placeholder-opacity-* | placeholder-black/* |
| flex-shrink-* | shrink-* |
| flex-grow-* | grow-* |
| overflow-ellipsis | text-ellipsis |
| decoration-slice | box-decoration-slice |
| decoration-clone | box-decoration-clone |

## Spacing

Use `gap` utilities instead of margins for spacing between siblings:

<!-- Gap Utilities -->
```html
<div class="flex gap-8">
    <div>Item 1</div>
    <div>Item 2</div>
</div>
```

## Dark Mode

If existing pages and components support dark mode, new pages and components must support it the same way, typically using the `dark:` variant:

<!-- Dark Mode -->
```html
<div class="bg-white dark:bg-gray-900 text-gray-900 dark:text-white">
    Content adapts to color scheme
</div>
```

## Common Patterns

### Flexbox Layout

<!-- Flexbox Layout -->
```html
<div class="flex items-center justify-between gap-4">
    <div>Left content</div>
    <div>Right content</div>
</div>
```

### Grid Layout

<!-- Grid Layout -->
```html
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
    <div>Card 1</div>
    <div>Card 2</div>
    <div>Card 3</div>
</div>
```

## Common Pitfalls

- Using deprecated v3 utilities (bg-opacity-*, flex-shrink-*, etc.)
- Using `@tailwind` directives instead of `@import "tailwindcss"`
- Trying to use `tailwind.config.js` instead of CSS `@theme` directive
- Using margins for spacing between siblings instead of gap utilities
- Forgetting to add dark mode variants when the project uses dark mode


laravel/framework livewire/livewire livewire/flux pestphp/pest tailwindcss

migration foreign key nullable constrained parent id

livewire forms nested arrays select testing


# Search Results

## Forms Extracting input fields to Blade components
Forms | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/forms

## Extracting input fields to Blade components

Even in a small component such as the `post.create` example we've been discussing, we end up duplicating lots of form field boilerplate like validation messages and labels.

It can be helpful to extract repetitive UI elements such as these into dedicated [Blade components](https://laravel.com/docs/blade#components) to be shared across your application.

For example, below is the original Blade template from the `post.create` component. We will be extracting the following two text inputs into dedicated Blade components:

```blade
<form wire:submit="save">
    <input type="text" wire:model="title"> <!-- [tl! highlight:3] -->
    <div>
        @error('title') <span class="error">{{ $message }}</span> @enderror
    </div>

    <input type="text" wire:model="content"> <!-- [tl! highlight:3] -->
    <div>
        @error('content') <span class="error">{{ $message }}</span> @enderror
    </div>

    <button type="submit">Save</button>
</form>
```

Here's what the template will look like after extracting a re-usable Blade component called `<x-input-text>`:

```blade
<form wire:submit="save">
    <x-input-text name="title" wire:model="title" /> <!-- [tl! highlight] -->

    <x-input-text name="content" wire:model="content" /> <!-- [tl! highlight] -->

    <button type="submit">Save</button>
</form>
```

Next, here's the source for the `x-input-text` component:

```blade
<!-- resources/views/components/input-text.blade.php -->

@props(['name'])

<input type="text" name="{{ $name }}" {{ $attributes }}>

<div>
    @error($name) <span class="error">{{ $message }}</span> @enderror
</div>
```

As you can see, we took the repetitive HTML and placed it inside a dedicated Blade component.

For the most part, the Blade component contains only the extracted HTML from the original component. However, we have added two things:

* The `@props` directive
* The `{{ $attributes }}` statement on the input

Let's discuss each of these additions:

By specifying `name` as a "prop" using `@props(['name'])` we are telling Blade: if an attribute called "name" is set on this component, take its value and make it available inside this component as `$name`.

For other attributes that don't have an explicit purpose, we used the `{{ $attributes }}` statement. This is used for "attribute forwarding", or in other words, taking any HTML attributes written on the Blade component and forwarding them onto an element within the component.

This ensures `wire:model="title"` and any other extra attributes such as `disabled`, `class="..."`, or `required` still get forwarded to the actual `<input>` element.

### Custom form controls

In the previous example, we "wrapped" an input element into a re-usable Blade component we can use as if it was a native HTML input element.

This pattern is very useful; however, there might be some cases where you want to create an entire input component from scratch (without an underlying native input element), but still be able to bind its value to Livewire properties using `wire:model`.

For example, let's imagine you wanted to create an `<x-input-counter />` component that was a simple "counter" input written in Alpine.

Before we create a Blade component, let's first look at a simple, pure-Alpine, "counter" component for reference:

```blade
<div x-data="{ count: 0 }">
    <button x-on:click="count--">-</button>

    <span x-text="count"></span>

    <button x-on:click="count++">+</button>
</div>
```

As you can see, the component above shows a number alongside two buttons to increment and decrement that number.

Now, let's imagine we want to extract this component into a Blade component called `<x-input-counter />` that we would use within a component like so:

```blade
<x-input-counter wire:model="quantity" />
```

Creating this component is mostly simple. We take the HTML of the counter and place it inside a Blade component template like `resources/views/components/input-counter.blade.php`.

However, making it work with `wire:model="quantity"` so that you can easily bind data from your Livewire component to the "count" inside this Alpine component needs one extra step.

Here's the source for the component:

```blade
<!-- resources/view/components/input-counter.blade.php -->

<div x-data="{ count: 0 }" x-modelable="count" {{ $attributes}}>
    <button x-on:click="count--">-</button>

    <span x-text="count"></span>

    <button x-on:click="count++">+</button>
</div>
```

As you can see, the only different bit about this HTML is the `x-modelable="count"` and `{{ $attributes }}`.

`x-modelable` is a utility in Alpine that tells Alpine to make a certain piece of data available for binding from outside. [The Alpine documentation has more information on this directive.](https://alpinejs.dev/directives/modelable)

`{{ $attributes }}`, as we explored earlier, forwards any attributes passed into the Blade component from outside. In this case, the `wire:model` directive will be forwarded.

Because of `{{ $attributes }}`, when the HTML is rendered in the browser, `wire:model="quantity"` will be rendered alongside `x-modelable="count"` on the root `<div>` of the Alpine component like so:

```blade
<div x-data="{ count: 0 }" x-modelable="count" wire:model="quantity">
```

`x-modelable="count"` tells Alpine to look for any `x-model` or `wire:model` statements and use "count" as the data to bind them to.

Because `x-modelable` works for both `wire:model` and `x-model`, you can also use this Blade component interchangeably with Livewire and Alpine. Here’s an example of using this Blade component in a purely Alpine context:

```blade
<x-input-counter x-model="quantity" />
```

Creating custom input elements in your application is extremely powerful but requires a deeper understanding of the utilities Livewire and Alpine provide and how they interact with each other.

---

## Wire Model Reference
Wire Model | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/wire-model

## Reference

```blade
wire:model="propertyName"
wire:model="property.nested"
wire:model="property['nested']"
wire:model="property[0]"
```

### Modifiers

| Modifier | Description |
|----------|-------------|
| `.live` | Send updates to the server |
| `.blur` | Only update on blur |
| `.change` | Only update on change |
| `.enter` | Only update on enter key |
| `.lazy` | Update on change and send network request (v3 compatible) |
| `.debounce.Xms` | Debounce updates (use with `.live`) |
| `.throttle.Xms` | Throttle updates (use with `.live`) |
| `.number` | Cast value to `int` on the server |
| `.boolean` | Cast value to `bool` on the server |
| `.fill` | Use initial value from HTML `value` attribute |
| `.deep` | Also listen to events from child elements |
| `.preserve-scroll` | Maintain scroll position during updates |

---

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

### Individual transform properties

The `rotate-*`, `scale-*`, and `translate-*` utilities are now based on the individual `rotate`, `scale`, and `translate` properties in CSS. Normally this shouldn't affect the behavior but there's a couple of cases to look out for:

#### Resetting Transforms

You previously would've been able to "reset" your rotate, scale, and translate utilities via `transform-none`. This no longer works and you will need to reset the individual properties instead:

```html
<!-- [!code filename:HTML] -->
<!-- [!code --:2] -->
<button class="scale-150 focus:transform-none"></button>
<!-- [!code ++:2] -->
<button class="scale-150 focus:scale-none"></button>
```

#### Transitions

If you customize the list of transitioned properties and include `transform` (e.g. by writing `transition-[opacity,transform]`) then these utilities will no longer transition. To fix this, include the individual properties in the list. For example, if you want to transition changes when using `opacity-*` and `scale-*` utilities you should use `transition-[opacity,scale]` instead.

```html
<!-- [!code filename:HTML] -->
<!-- [!code --:2] -->
<button class="transition-[opacity,transform] hover:scale-150"></button>
<!-- [!code ++:2] -->
<button class="transition-[opacity,scale] hover:scale-150"></button>
```

---

## Properties Security concerns
Properties | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/properties

## Security concerns

While Livewire properties are a powerful feature, there are a few security considerations that you should be aware of before using them.

In short, always treat public properties as user input — as if they were request input from a traditional endpoint. In light of this, it's essential to validate and authorize properties before persisting them to a database — just like you would do when working with request input in a controller.

### Don't trust property values

To demonstrate how neglecting to authorize and validate properties can introduce security holes in your application, the following `post.edit` component is vulnerable to attack:

```php
<?php // resources/views/components/post/⚡edit.blade.php

use Livewire\Component;
use App\Models\Post;

new class extends Component {
    public $id;
    public $title;
    public $content;

    public function mount(Post $post)
    {
        $this->id = $post->id;
        $this->title = $post->title;
        $this->content = $post->content;
    }

    public function update()
    {
        $post = Post::findOrFail($this->id);

        $post->update([
            'title' => $this->title,
            'content' => $this->content,
        ]);

        session()->flash('message', 'Post updated successfully!');
    }
};
```

```blade
<form wire:submit="update">
    <input type="text" wire:model="title">
    <input type="text" wire:model="content">

    <button type="submit">Update</button>
</form>
```

At first glance, this component may look completely fine. But, let's walk through how an attacker could use the component to do unauthorized things in your application.

Because we are storing the `id` of the post as a public property on the component, it can be manipulated on the client just the same as the `title` and `content` properties.

It doesn't matter that we didn't write an input with `wire:model="id"`. A malicious user can easily change the view to the following using their browser DevTools:

```blade
<form wire:submit="update">
    <input type="text" wire:model="id"> <!-- [tl! highlight] -->
    <input type="text" wire:model="title">
    <input type="text" wire:model="content">

    <button type="submit">Update</button>
</form>
```

Now the malicious user can update the `id` input to the ID of a different post model. When the form is submitted and `update()` is called, `Post::findOrFail()` will return and update a post the user is not the owner of.

To prevent this kind of attack, we can use one or both of the following strategies:

* Authorize the input
* Lock the property from updates

#### Authorizing the input

Because `$id` can be manipulated client-side with something like `wire:model`, just like in a controller, we can use [Laravel's authorization](https://laravel.com/docs/authorization) to make sure the current user can update the post:

```php
public function update()
{
    $post = Post::findOrFail($this->id);

    $this->authorize('update', $post); // [tl! highlight]

    $post->update(...);
}
```

If a malicious user mutates the `$id` property, the added authorization will catch it and throw an error.

#### Locking the property

Livewire also allows you to "lock" properties in order to prevent properties from being modified on the client-side. You can "lock" a property from client-side manipulation using the `#[Locked]` attribute:

```php
use Livewire\Attributes\Locked;
use Livewire\Component;

new class extends Component {
    #[Locked] // [tl! highlight]
    public $id;

    // ...
};
```

Now, if a user tries to modify `$id` on the front end, an error will be thrown.

By using `#[Locked]`, you can assume this property has not been manipulated anywhere outside your component's class.

For more information on locking properties, [consult the Locked attribute documentation](/docs/4.x/attribute-locked).

#### Eloquent models and locking

When an Eloquent model is assigned to a Livewire component property, Livewire will automatically lock the property and ensure the ID isn't changed, so that you are safe from these kinds of attacks:

```php
<?php // resources/views/components/post/⚡edit.blade.php

use Livewire\Component;
use App\Models\Post;

new class extends Component {
    public Post $post; // [tl! highlight]
    public $title;
    public $content;

    public function mount(Post $post)
    {
        $this->post = $post;
        $this->title = $post->title;
        $this->content = $post->content;
    }

    public function update()
    {
        $this->post->update([
            'title' => $this->title,
            'content' => $this->content,
        ]);

        session()->flash('message', 'Post updated successfully!');
    }
};
```

### Properties expose system information to the browser

Another essential thing to remember is that Livewire properties are serialized or "dehydrated" before they are sent to the browser. This means that their values are converted to a format that can be sent over the wire and understood by JavaScript. This format can expose information about your application to the browser, including the names and class names of your properties.

For example, suppose you have a Livewire component that defines a public property named `$post`. This property contains an instance of a `Post` model from your database. In this case, the dehydrated value of this property sent over the wire might look something like this:

```json
{
    "type": "model",
    "class": "App\Models\Post",
    "key": 1,
    "relationships": []
}
```

As you can see, the dehydrated value of the `$post` property includes the class name of the model (`App\Models\Post`) as well as the ID and any relationships that have been eager-loaded.

If you don't want to expose the class name of the model, you can use Laravel's "morphMap" functionality from a service provider to assign an alias to a model class name:

```php
<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use Illuminate\Database\Eloquent\Relations\Relation;

class AppServiceProvider extends ServiceProvider
{
    public function boot()
    {
        Relation::morphMap([
            'post' => 'App\Models\Post',
        ]);
    }
}
```

Now, when the Eloquent model is "dehydrated" (serialized), the original class name won't be exposed, only the "post" alias:

```json
{
    "type": "model",
    "class": "App\Models\Post", // [tl! remove]
    "class": "post", // [tl! add]
    "key": 1,
    "relationships": []
}
```

### Eloquent constraints aren't preserved between requests

Typically, Livewire is able to preserve and recreate server-side properties between requests; however, there are certain scenarios where preserving values are impossible between requests.

For example, when storing Eloquent collections as Livewire properties, additional query constraints like `select(...)` will not be re-applied on subsequent requests.

To demonstrate, consider the following `show-todos` component with a `select()` constraint applied to the `Todos` Eloquent collection:

```php
<?php // resources/views/components/⚡show-todos.blade.php

use Illuminate\Support\Facades\Auth;
use Livewire\Component;

new class extends Component {
    public $todos;

    public function mount()
    {
        $this->todos = Auth::user()
            ->todos()
            ->select(['title', 'content']) // [tl! highlight]
            ->get();
    }
};
```

When this component is initially loaded, the `$todos` property will be set to an Eloquent collection of the user's todos; however, only the `title` and `content` fields of each row in the database will have been queried and loaded into each of the models.

When Livewire _hydrates_ the JSON of this property back into PHP on a subsequent request, the select constraint will have been lost.

To ensure the integrity of Eloquent queries, we recommend that you use [computed properties](/docs/4.x/computed-properties) instead of properties.

Computed properties are methods in your component marked with the `#[Computed]` attribute. They can be accessed as a dynamic property that isn't stored as part of the component's state but is instead evaluated on-the-fly.

Here's the above example re-written using a computed property:

```php
<?php // resources/views/components/⚡show-todos.blade.php

use Illuminate\Support\Facades\Auth;
use Livewire\Attributes\Computed;
use Livewire\Component;

new class extends Component {
    #[Computed] // [tl! highlight]
    public function todos()
    {
        return Auth::user()
            ->todos()
            ->select(['title', 'content'])
            ->get();
    }
};
```

Here's how you would access these _todos_ from the Blade view:

```blade
<ul>
    @foreach ($this->todos as $todo)
        <li wire:key="{{ $loop->index }}">{{ $todo }}</li>
    @endforeach
</ul>
```

Notice, inside your views, you can only access computed properties on the `$this` object like so: `$this->todos`.

You can also access `$todos` from inside your class. For example, if you had a `markAllAsComplete()` action:

```php
<?php // resources/views/components/⚡show-todos.blade.php

use Illuminate\Support\Facades\Auth;
use Livewire\Attributes\Computed;
use Livewire\Component;

new class extends Component {
    #[Computed]
    public function todos()
    {
        return Auth::user()
            ->todos()
            ->select(['title', 'content'])
            ->get();
    }

    public function markAllComplete() // [tl! highlight:3]
    {
        $this->todos->each->complete();
    }
};
```

You might wonder why not just call `$this->todos()` as a method directly where you need to? Why use `#[Computed]` in the first place?

The reason is that computed properties have a performance advantage, since they are automatically memoized after their first usage during a single request. This means you can freely access `$this->todos` within your component and be assured that the actual method will only be called once, so that you don't run an expensive query multiple times in the same request.

For more information, [visit the computed properties documentation](/docs/4.x/computed-properties).

---

## Migrating From Phpunit Guide
Migrating From Phpunit Guide | pestphp/pest@4.x | Source: https://github.com/pestphp/docs/blob/4.x/migrating-from-phpunit-guide.md

---
title: Migrating from PHPUnit
description: Migrating from PHPUnit to Pest is a simple process that can be completed in just a few steps.
---

# Migrating from PHPUnit

Pest is built on top of PHPUnit, so migrating from PHPUnit to Pest is a simple process that can be completed in just a few steps. Once you have Pest installed, you should require the `pestphp/pest-plugin-drift` package as a "dev" dependency in your project.

```bash
composer require pestphp/pest-plugin-drift --dev
```

Drift is a simple yet powerful plugin that will automatically convert your PHPUnit tests to Pest, simply by running the `--drift` option.

```bash
./vendor/bin/pest --drift
```

So, typically, a PHPUnit test looks like this:

```php
<?php

namespace Tests\Unit;

use PHPUnit\Framework\TestCase;

class ExampleTest extends TestCase
{
    public function test_that_true_is_true(): void
    {
        $this->assertTrue(true);
    }
}
```

Should look like this after running `--drift`:

```php
test('true is true', function () {
    expect(true)->toBeTrue();
});
```

---
## Convert Phpunit tests only for certain folder

If you want to convert Phpunit tests only for certain folder, you can pass _path_ as first argument when calling `--drift`:

Example call, if you want to run the conversion for folder `/tests/Helpers`:
```console
/vendor/bin/pest --drift tests/Helpers
```

Output:
```console
/vendor/bin/pest --drift tests/Helpers

✔✔✔✔✔✔✔✔✔✔✔✔✔✔✔✔✔✔✔✔✔✔✔✔✔✔

INFO  The [tests/Helpers] directory has been migrated to PEST with XY files changed.
```

---

The output will contain a summary of the conversion process, as well as a list of the files that were converted.

While most of your tests should be converted automatically, and you should be able to run them without any issues, there are some cases where you may need to manually convert some of your tests.

---

Of course, this particular chapter is only for those who are migrating from PHPUnit. Next, let's learn how you can contribute to the growth of Pest: [Community Guide](/docs/community-guide)

---

## Migrations Indexes Foreign Key Constraints
Migrations | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/migrations.md

<a name="foreign-key-constraints"></a>
### Foreign Key Constraints

Laravel also provides support for creating foreign key constraints, which are used to force referential integrity at the database level. For example, let's define a `user_id` column on the `posts` table that references the `id` column on a `users` table:

```php
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

Schema::table('posts', function (Blueprint $table) {
    $table->unsignedBigInteger('user_id');

    $table->foreign('user_id')->references('id')->on('users');
});
```

Since this syntax is rather verbose, Laravel provides additional, terser methods that use conventions to provide a better developer experience. When using the `foreignId` method to create your column, the example above can be rewritten like so:

```php
Schema::table('posts', function (Blueprint $table) {
    $table->foreignId('user_id')->constrained();
});
```

The `foreignId` method creates an `UNSIGNED BIGINT` equivalent column, while the `constrained` method will use conventions to determine the table and column being referenced. If your table name does not match Laravel's conventions, you may manually provide it to the `constrained` method. In addition, the name that should be assigned to the generated index may be specified as well:

```php
Schema::table('posts', function (Blueprint $table) {
    $table->foreignId('user_id')->constrained(
        table: 'users', indexName: 'posts_user_id'
    );
});
```

You may also specify the desired action for the "on delete" and "on update" properties of the constraint:

```php
$table->foreignId('user_id')
    ->constrained()
    ->onUpdate('cascade')
    ->onDelete('cascade');
```

An alternative, expressive syntax is also provided for these actions:

<div class="overflow-auto">

| Method                        | Description                                       |
| ----------------------------- | ------------------------------------------------- |
| `$table->cascadeOnUpdate();`  | Updates should cascade.                           |
| `$table->restrictOnUpdate();` | Updates should be restricted.                     |
| `$table->nullOnUpdate();`     | Updates should set the foreign key value to null. |
| `$table->noActionOnUpdate();` | No action on updates.                             |
| `$table->cascadeOnDelete();`  | Deletes should cascade.                           |
| `$table->restrictOnDelete();` | Deletes should be restricted.                     |
| `$table->nullOnDelete();`     | Deletes should set the foreign key value to null. |
| `$table->noActionOnDelete();` | Prevents deletes if child records exist.          |

</div>

Any additional [column modifiers](#column-modifiers) must be called before the `constrained` method:

```php
$table->foreignId('user_id')
    ->nullable()
    ->constrained();
```

---

## Transform Examples
Transform | tailwindcss@4.x | Source: https://github.com/tailwindlabs/tailwindcss.com/blob/main/src/docs/transform.mdx

## Examples

### Hardware acceleration

If your transition performs better when rendered by the GPU instead of the CPU, you can force hardware acceleration by adding the `transform-gpu` utility:

```html
<!-- [!code classes:transform-gpu] -->
<div class="scale-150 transform-gpu">
  <!-- ... -->
</div>
```

Use the `transform-cpu` utility to force things back to the CPU if you need to undo this conditionally.

### Removing transforms

Use the `transform-none` utility to remove all of the transforms on an element at once:

```html
<!-- [!code classes:transform-none] -->
<div class="skew-y-3 md:transform-none">
  <!-- ... -->
</div>
```

### Using a custom value

<UsingACustomValue utility="transform" value="matrix(1,2,3,4,5,6)" />

---

## Uploads
Uploads | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/uploads

Livewire offers powerful support for uploading files within your components.

First, add the `WithFileUploads` trait to your component. Once this trait has been added to your component, you can use `wire:model` on file inputs as if they were any other input type and Livewire will take care of the rest.

Here's an example of a simple component that handles uploading a photo:

```php
<?php // resources/views/components/⚡upload-photo.blade.php

use Livewire\Attributes\Validate;
use Livewire\WithFileUploads;
use Livewire\Component;

new class extends Component {
    use WithFileUploads;

    #[Validate('image|max:1024')] // 1MB Max
    public $photo;

    public function save()
    {
        $this->photo->store(path: 'photos');
    }
};
```

```blade
<form wire:submit="save">
    <input type="file" wire:model="photo">

    @error('photo') <span class="error">{{ $message }}</span> @enderror

    <button type="submit">Save photo</button>
</form>
```

> [!warning] The "upload" method is reserved
> Notice the above example uses a "save" method instead of an "upload" method. This is a common "gotcha". The term "upload" is reserved by Livewire. You cannot use it as a method or property name in your component.

From the developer's perspective, handling file inputs is no different than handling any other input type: Add `wire:model` to the `<input>` tag and everything else is taken care of for you.

However, more is happening under the hood to make file uploads work in Livewire. Here's a glimpse at what goes on when a user selects a file to upload:

1. When a new file is selected, Livewire's JavaScript makes an initial request to the component on the server to get a temporary "signed" upload URL.
2. Once the URL is received, JavaScript does the actual "upload" to the signed URL, storing the upload in a temporary directory designated by Livewire and returning the new temporary file's unique hash ID.
3. Once the file is uploaded and the unique hash ID is generated, Livewire's JavaScript makes a final request to the component on the server, telling it to "set" the desired public property to the new temporary file.
4. Now, the public property (in this case, `$photo`) is set to the temporary file upload and is ready to be stored or validated at any point.

---

## Events Dispatching events from Blade templates
Events | livewire/livewire@4.x | Source: https://livewire.laravel.com/docs/4.x/events

## Dispatching events from Blade templates

You can dispatch events directly from your Blade templates using the `$dispatch` JavaScript function. This is useful when you want to trigger an event from a user interaction, such as a button click:

```blade
<button wire:click="$dispatch('show-post-modal', { id: {{ $post->id }} })">
    EditPost
</button>
```

In this example, when the button is clicked, the `show-post-modal` event will be dispatched with the specified data.

If you want to dispatch an event directly to another component you can use the `$dispatchTo()` JavaScript function:

```blade
<button wire:click="$dispatchTo('posts', 'show-post-modal', { id: {{ $post->id }} })">
    EditPost
</button>
```

In this example, when the button is clicked, the `show-post-modal` event will be dispatched directly to the `Posts` component.

_[Truncated]_