Output:
Chunk ID: 95c2df
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 134
Output:
 .../Api/Admin/V1/ShippingRateController.php        | 105 ++++++++++++++
 .../Api/Admin/V1/ShippingZoneController.php        | 159 +++++++++++++++++++++
 .../Resources/Admin/V1/ShippingRateResource.php    |  69 +++++++++
 .../Resources/Admin/V1/ShippingZoneResource.php    |  26 ++++
 routes/api.php                                     |   6 +
 specs/progress.md                                  |  16 ++-
 tests/Feature/Api/AdminShippingSettingsApiTest.php | 147 +++++++++++++++++++
 7 files changed, 524 insertions(+), 4 deletions(-)


Output:
Chunk ID: a77efd
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 84
Output:
A  app/Http/Controllers/Api/Admin/V1/ShippingRateController.php
A  app/Http/Controllers/Api/Admin/V1/ShippingZoneController.php
A  app/Http/Resources/Admin/V1/ShippingRateResource.php
A  app/Http/Resources/Admin/V1/ShippingZoneResource.php
M  routes/api.php
M  specs/progress.md
A  tests/Feature/Api/AdminShippingSettingsApiTest.php


git commit -m "Add admin shipping settings API"


git commit -m "Add admin shipping settings API"

[2026-05-04-codex-5-5-goal 95ad3071] Add admin shipping settings API
 7 files changed, 524 insertions(+), 4 deletions(-)
 create mode 100644 app/Http/Controllers/Api/Admin/V1/ShippingRateController.php
 create mode 100644 app/Http/Controllers/Api/Admin/V1/ShippingZoneController.php
 create mode 100644 app/Http/Resources/Admin/V1/ShippingRateResource.php
 create mode 100644 app/Http/Resources/Admin/V1/ShippingZoneResource.php
 create mode 100644 tests/Feature/Api/AdminShippingSettingsApiTest.php


Output:
Chunk ID: 0584ae
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 125
Output:
[2026-05-04-codex-5-5-goal 95ad3071] Add admin shipping settings API
 7 files changed, 524 insertions(+), 4 deletions(-)
 create mode 100644 app/Http/Controllers/Api/Admin/V1/ShippingRateController.php
 create mode 100644 app/Http/Controllers/Api/Admin/V1/ShippingZoneController.php
 create mode 100644 app/Http/Resources/Admin/V1/ShippingRateResource.php
 create mode 100644 app/Http/Resources/Admin/V1/ShippingZoneResource.php
 create mode 100644 tests/Feature/Api/AdminShippingSettingsApiTest.php


Committed `95ad3071` for shipping settings. Remaining REST gaps are general settings, themes, and exports; I’m checking the theme model/services next to see whether publish/settings can be implemented without adding a new package or archive parser.


file upload validation zip json API testing

route model binding scoped update settings validation array


sed -n '2498,2595p' specs/02-API-ROUTES.md


sed -n '1,220p' app/Models/ThemeSettings.php


rg -n "ThemeSettingsService|publish|Theme::|themes" app/Livewire app/Services database tests -g '*.php'


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


# Search Results

## Http Tests Testing JSON APIs
Http Tests | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/http-tests.md

## Testing JSON APIs

Laravel also provides several helpers for testing JSON APIs and their responses. For example, the `json`, `getJson`, `postJson`, `putJson`, `patchJson`, `deleteJson`, and `optionsJson` methods may be used to issue JSON requests with various HTTP verbs. You may also easily pass data and headers to these methods. To get started, let's write a test to make a `POST` request to `/api/user` and assert that the expected JSON data was returned:

```php tab=Pest
<?php

test('making an api request', function () {
    $response = $this->postJson('/api/user', ['name' => 'Sally']);

    $response
        ->assertStatus(201)
        ->assertJson([
            'created' => true,
        ]);
});
```

```php tab=PHPUnit
<?php

namespace Tests\Feature;

use Tests\TestCase;

class ExampleTest extends TestCase
{
    /**
     * A basic functional test example.
     */
    public function test_making_an_api_request(): void
    {
        $response = $this->postJson('/api/user', ['name' => 'Sally']);

        $response
            ->assertStatus(201)
            ->assertJson([
                'created' => true,
            ]);
    }
}
```

In addition, JSON response data may be accessed as array variables on the response, making it convenient for you to inspect the individual values returned within a JSON response:

```php tab=Pest
expect($response['created'])->toBeTrue();
```

```php tab=PHPUnit
$this->assertTrue($response['created']);
```

> [!NOTE]
> The `assertJson` method converts the response to an array to verify that the given array exists within the JSON response returned by the application. So, if there are other properties in the JSON response, this test will still pass as long as the given fragment is present.

---

## Mutation Testing Options & Modifiers `--profile`
Mutation Testing | pestphp/pest@4.x | Source: https://github.com/pestphp/docs/blob/4.x/mutation-testing.md

### `--profile`

Output to standard output the top ten slowest mutations.

```bash
./vendor/bin/pest --mutate --profile
```

---

## Vite Working With Blade and Routes Aliases
Vite | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/vite.md

### Aliases

It is common in JavaScript applications to [create aliases](#aliases) to regularly referenced directories. But, you may also create aliases to use in Blade by using the `macro` method on the `Illuminate\Support\Facades\Vite` class. Typically, "macros" should be defined within the `boot` method of a [service provider](/docs/{{version}}/providers):

```php
/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    Vite::macro('image', fn (string $asset) => $this->asset("resources/images/{$asset}"));
}
```

Once a macro has been defined, it can be invoked within your templates. For example, we can use the `image` macro defined above to reference an asset located at `resources/images/logo.png`:

```blade
<img src="{{ Vite::image('logo.png') }}" alt="Laravel Logo">
```

---

## Expectations `toBeWritableFile()`
Expectations | pestphp/pest@4.x | Source: https://github.com/pestphp/docs/blob/4.x/expectations.md

### `toBeWritableFile()`

This expectation ensures that the string `$value` is a file and that it is writable.

```php
expect('/tmp/some-file.tmp')->toBeWritableFile();
```

---

## Vite Working With Blade and Routes Refreshing on Save
Vite | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/vite.md

### Refreshing on Save

When your application is built using traditional server-side rendering with Blade, Vite can improve your development workflow by automatically refreshing the browser when you make changes to view files in your application. To get started, you can simply specify the `refresh` option as `true`.

```js
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';

export default defineConfig({
    plugins: [
        laravel({
            // ...
            refresh: true,
        }),
    ],
});
```

When the `refresh` option is `true`, saving files in the following directories will trigger the browser to perform a full page refresh while you are running `npm run dev`:

- `app/Livewire/**`
- `app/View/Components/**`
- `lang/**`
- `resources/lang/**`
- `resources/views/**`
- `routes/**`

Watching the `routes/**` directory is useful if you are utilizing [Ziggy](https://github.com/tighten/ziggy) to generate route links within your application's frontend.

If these default paths do not suit your needs, you can specify your own list of paths to watch:

```js
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';

export default defineConfig({
    plugins: [
        laravel({
            // ...
            refresh: ['resources/views/**'],
        }),
    ],
});
```

Under the hood, the Laravel Vite plugin uses the [vite-plugin-full-reload](https://github.com/ElMassimo/vite-plugin-full-reload) package, which offers some advanced configuration options to fine-tune this feature's behavior. If you need this level of customization, you may provide a `config` definition:

```js
import { defineConfig } from 'vite';
import laravel from 'laravel-vite-plugin';

export default defineConfig({
    plugins: [
        laravel({
            // ...
            refresh: [{
                paths: ['path/to/watch/**'],
                config: { delay: 300 }
            }],
        }),
    ],
});
```

---

## Validation Validation Quickstart Validation Error Response Format
Validation | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/validation.md

### Validation Error Response Format

When your application throws a `Illuminate\Validation\ValidationException` exception and the incoming HTTP request is expecting a JSON response, Laravel will automatically format the error messages for you and return a `422 Unprocessable Entity` HTTP response.

Below, you can review an example of the JSON response format for validation errors. Note that nested error keys are flattened into "dot" notation format:

```json
{
    "message": "The team name must be a string. (and 4 more errors)",
    "errors": {
        "team_name": [
            "The team name must be a string.",
            "The team name must be at least 1 characters."
        ],
        "authorization.role": [
            "The selected authorization.role is invalid."
        ],
        "users.0.email": [
            "The users.0.email field is required."
        ],
        "users.2.email": [
            "The users.2.email must be a valid email address."
        ]
    }
}
```

---

## Requests Files Retrieving Uploaded Files Validating Successful Uploads
Requests | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/requests.md

#### Validating Successful Uploads

In addition to checking if the file is present, you may verify that there were no problems uploading the file via the `isValid` method:

```php
if ($request->file('photo')->isValid()) {
    // ...
}
```

---

## Routing Route Model Binding Implicit Binding Custom Keys and Scoping
Routing | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/routing.md

#### Custom Keys and Scoping

When implicitly binding multiple Eloquent models in a single route definition, you may wish to scope the second Eloquent model such that it must be a child of the previous Eloquent model. For example, consider this route definition that retrieves a blog post by slug for a specific user:

```php
use App\Models\Post;
use App\Models\User;

Route::get('/users/{user}/posts/{post:slug}', function (User $user, Post $post) {
    return $post;
});
```

When using a custom keyed implicit binding as a nested route parameter, Laravel will automatically scope the query to retrieve the nested model by its parent using conventions to guess the relationship name on the parent. In this case, it will be assumed that the `User` model has a relationship named `posts` (the plural form of the route parameter name) which can be used to retrieve the `Post` model.

If you wish, you may instruct Laravel to scope "child" bindings even when a custom key is not provided. To do so, you may invoke the `scopeBindings` method when defining your route:

```php
use App\Models\Post;
use App\Models\User;

Route::get('/users/{user}/posts/{post}', function (User $user, Post $post) {
    return $post;
})->scopeBindings();
```

Or, you may instruct an entire group of route definitions to use scoped bindings:

```php
Route::scopeBindings()->group(function () {
    Route::get('/users/{user}/posts/{post}', function (User $user, Post $post) {
        return $post;
    });
});
```

Similarly, you may explicitly instruct Laravel to not scope bindings by invoking the `withoutScopedBindings` method:

```php
Route::get('/users/{user}/posts/{post:slug}', function (User $user, Post $post) {
    return $post;
})->withoutScopedBindings();
```

---

## Expectations `toBeReadableFile()`
Expectations | pestphp/pest@4.x | Source: https://github.com/pestphp/docs/blob/4.x/expectations.md

### `toBeReadableFile()`

This expectation ensures that the string `$value` is a file and that it is readable.

```php
expect('/tmp/some-file.tmp')->toBeReadableFile();
```

---

## Vite Working With Blade and Routes Processing Static Assets With Vite
Vite | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/vite.md

### Processing Static Assets With Vite

When referencing assets in your JavaScript or CSS, Vite automatically processes and versions them. In addition, when building Blade based applications, Vite can also process and version static assets that you reference solely in Blade templates.

However, in order to accomplish this, you need to make Vite aware of your assets by importing the static assets into the application's entry point. For example, if you want to process and version all images stored in `resources/images` and all fonts stored in `resources/fonts`, you should add the following in your application's `resources/js/app.js` entry point:

```js
import.meta.glob([
  '../images/**',
  '../fonts/**',
]);
```

These assets will now be processed by Vite when running `npm run build`. You can then reference these assets in Blade templates using the `Vite::asset` method, which will return the versioned URL for a given asset:

```blade
<img src="{{ Vite::asset('resources/images/logo.png') }}">
```

---

## Routing Rate Limiting Defining Rate Limiters Response-Based Rate Limiting
Routing | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/routing.md

#### Response-Based Rate Limiting

In addition to rate limiting incoming requests, Laravel allows you to rate limit based on the response using the `after` method. This is useful when you only want to count certain responses toward the rate limit, such as validation errors, 404 responses, or other specific HTTP status codes.

The `after` method accepts a closure that receives the response and should return `true` if the response should be counted toward the rate limit, or `false` if it should be ignored. This is particularly useful for preventing enumeration attacks by limiting consecutive 404 responses, or allowing users to retry requests that fail validation without exhausting their rate limit on an endpoint that should only throttle successful operations:

```php
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\RateLimiter;
use Symfony\Component\HttpFoundation\Response;

RateLimiter::for('resource-not-found', function (Request $request) {
    return Limit::perMinute(10)
        ->by($request->user()?->id ?: $request->ip())
        ->after(function (Response $response) {
            // Only count 404 responses toward the rate limit to prevent enumeration...
            return $response->status() === 404;
        });
});
```

---

## Validation Validating Files
Validation | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/validation.md

## Validating Files

Laravel provides a variety of validation rules that may be used to validate uploaded files, such as `mimes`, `image`, `min`, and `max`. While you are free to specify these rules individually when validating files, Laravel also offers a fluent file validation rule builder that you may find convenient:

```php
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rules\File;

Validator::validate($input, [
    'attachment' => [
        'required',
        File::types(['mp3', 'wav'])
            ->min(1024)
            ->max(12 * 1024),
    ],
]);
```

---

## Vite Working With Blade and Routes
Vite | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/vite.md

## Working With Blade and Routes

---

## Expectations `toBeFile()`
Expectations | pestphp/pest@4.x | Source: https://github.com/pestphp/docs/blob/4.x/expectations.md

### `toBeFile()`

This expectation ensures that the string `$value` is an existing file.

```php
expect('/tmp/some-file.tmp')->toBeFile();
```

---

## Responses Streamed Responses Streamed Downloads
Responses | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/responses.md

### Streamed Downloads

Sometimes you may wish to turn the string response of a given operation into a downloadable response without having to write the contents of the operation to disk. You may use the `streamDownload` method in this scenario. This method accepts a callback, filename, and an optional array of headers as its arguments:

```php
use App\Services\GitHub;

return response()->streamDownload(function () {
    echo GitHub::api('repo')
        ->contents()
        ->readme('laravel', 'laravel')['contents'];
}, 'laravel-readme.md');
```

---

## Routing Route Model Binding
Routing | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/routing.md

## Route Model Binding

When injecting a model ID to a route or controller action, you will often query the database to retrieve the model that corresponds to that ID. Laravel route model binding provides a convenient way to automatically inject the model instances directly into your routes. For example, instead of injecting a user's ID, you can inject the entire `User` model instance that matches the given ID.

---

## Validation Available Validation Rules file
Validation | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/validation.md

#### file

The field under validation must be a successfully uploaded file.

---

## Arch Testing Expectations `toHaveFileSystemPermissions()`
Arch Testing | pestphp/pest@4.x | Source: https://github.com/pestphp/docs/blob/4.x/arch-testing.md

### `toHaveFileSystemPermissions()`

The `toHaveFileSystemPermissions()` method may be used to ensure that all files within a given namespace have specific file system permissions.

```php
arch('app')
    ->expect('App')
    ->not->toHaveFileSystemPermissions('0777');
```

---

## Http Tests Available Assertions Response Assertions assertJson
Http Tests | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/http-tests.md

#### assertJson

Assert that the response contains the given JSON data:

```php
$response->assertJson(array $data, $strict = false);
```

The `assertJson` method converts the response to an array to verify that the given array exists within the JSON response returned by the application. So, if there are other properties in the JSON response, this test will still pass as long as the given fragment is present.

---

## Validation Validating Files Validating Image Dimensions
Validation | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/validation.md

#### Validating Image Dimensions

You may also validate the dimensions of an image. For example, to validate that an uploaded image is at least 1000 pixels wide and 500 pixels tall, you may use the `dimensions` rule:

```php
use Illuminate\Validation\Rule;
use Illuminate\Validation\Rules\File;

File::image()->dimensions(
    Rule::dimensions()
        ->maxWidth(1000)
        ->maxHeight(500)
)
```

> [!NOTE]
> More information regarding validating image dimensions may be found in the [dimension rule documentation](#rule-dimensions).

---

## Responses Other Response Types JSON Responses
Responses | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/responses.md

### JSON Responses

The `json` method will automatically set the `Content-Type` header to `application/json`, as well as convert the given array to JSON using the `json_encode` PHP function:

```php
return response()->json([
    'name' => 'Abigail',
    'state' => 'CA',
]);
```

If you would like to create a JSONP response, you may use the `json` method in combination with the `withCallback` method:

```php
return response()
    ->json(['name' => 'Abigail', 'state' => 'CA'])
    ->withCallback($request->input('callback'));
```

---

## Verification Routing Protecting Routes
Verification | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/verification.md

### Protecting Routes

[Route middleware](/docs/{{version}}/middleware) may be used to only allow verified users to access a given route. Laravel includes a `verified` [middleware alias](/docs/{{version}}/middleware#middleware-aliases), which is an alias for the `Illuminate\Auth\Middleware\EnsureEmailIsVerified` middleware class. Since this alias is already automatically registered by Laravel, all you need to do is attach the `verified` middleware to a route definition. Typically, this middleware is paired with the `auth` middleware:

```php
Route::get('/profile', function () {
    // Only verified users may access this route...
})->middleware(['auth', 'verified']);
```

If an unverified user attempts to access a route that has been assigned this middleware, they will automatically be redirected to the `verification.notice` [named route](/docs/{{version}}/routing#named-routes).

---

## Responses Response Macros
Responses | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/responses.md

## Response Macros

If you would like to define a custom response that you can re-use in a variety of your routes and controllers, you may use the `macro` method on the `Response` facade. Typically, you should call this method from the `boot` method of one of your application's [service providers](/docs/{{version}}/providers), such as the `App\Providers\AppServiceProvider` service provider:

```php
<?php

namespace App\Providers;

use Illuminate\Support\Facades\Response;
use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        Response::macro('caps', function (string $value) {
            return Response::make(strtoupper($value));
        });
    }
}
```

The `macro` function accepts a name as its first argument and a closure as its second argument. The macro's closure will be executed when calling the macro name from a `ResponseFactory` implementation or the `response` helper:

```php
return response()->caps('foo');
```

---

## Validation Validating Files Validating Image Files
Validation | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/validation.md

#### Validating Image Files

If your application accepts images uploaded by your users, you may use the `File` rule's `image` constructor method to ensure that the file under validation is an image (jpg, jpeg, png, bmp, gif, or webp).

In addition, the `dimensions` rule may be used to limit the dimensions of the image:

```php
use Illuminate\Support\Facades\Validator;
use Illuminate\Validation\Rule;
use Illuminate\Validation\Rules\File;

Validator::validate($input, [
    'photo' => [
        'required',
        File::image()
            ->min(1024)
            ->max(12 * 1024)
            ->dimensions(Rule::dimensions()->maxWidth(1000)->maxHeight(500)),
    ],
]);
```

> [!NOTE]
> More information regarding validating image dimensions may be found in the [dimension rule documentation](#rule-dimensions).

> [!WARNING]
> By default, the `image` rule does not allow SVG files due to the possibility of XSS vulnerabilities. If you need to allow SVG files, you may pass `allowSvg: true` to the `image` rule: `File::image(allowSvg: true)`.

---

## Validation Validating Files Validating File Sizes
Validation | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/validation.md

#### Validating File Sizes

For convenience, minimum and maximum file sizes may be specified as a string with a suffix indicating the file size units. The `kb`, `mb`, `gb`, and `tb` suffixes are supported:

```php
File::types(['mp3', 'wav'])
    ->min('1kb')
    ->max('10mb');
```

---

## Routing Route Model Binding Explicit Binding Customizing the Resolution Logic
Routing | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/routing.md

#### Customizing the Resolution Logic

If you wish to define your own model binding resolution logic, you may use the `Route::bind` method. The closure you pass to the `bind` method will receive the value of the URI segment and should return the instance of the class that should be injected into the route. Again, this customization should take place in the `boot` method of your application's `AppServiceProvider`:

```php
use App\Models\User;
use Illuminate\Support\Facades\Route;

/**
 * Bootstrap any application services.
 */
public function boot(): void
{
    Route::bind('user', function (string $value) {
        return User::where('name', $value)->firstOrFail();
    });
}
```

Alternatively, you may override the `resolveRouteBinding` method on your Eloquent model. This method will receive the value of the URI segment and should return the instance of the class that should be injected into the route:

```php
/**
 * Retrieve the model for a bound value.
 *
 * @param  mixed  $value
 * @param  string|null  $field
 * @return \Illuminate\Database\Eloquent\Model|null
 */
public function resolveRouteBinding($value, $field = null)
{
    return $this->where('name', $value)->firstOrFail();
}
```

If a route is utilizing [implicit binding scoping](#implicit-model-binding-scoping), the `resolveChildRouteBinding` method will be used to resolve the child binding of the parent model:

```php
/**
 * Retrieve the child model for a bound value.
 *
 * @param  string  $childType
 * @param  mixed  $value
 * @param  string|null  $field
 * @return \Illuminate\Database\Eloquent\Model|null
 */
public function resolveChildRouteBinding($childType, $value, $field)
{
    return parent::resolveChildRouteBinding($childType, $value, $field);
}
```

---

## Verification Routing Resending the Verification Email
Verification | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/verification.md

### Resending the Verification Email

Sometimes a user may misplace or accidentally delete the email address verification email. To accommodate this, you may wish to define a route to allow the user to request that the verification email be resent. You may then make a request to this route by placing a simple form submission button within your [verification notice view](#the-email-verification-notice):

```php
use Illuminate\Http\Request;

Route::post('/email/verification-notification', function (Request $request) {
    $request->user()->sendEmailVerificationNotification();

    return back()->with('message', 'Verification link sent!');
})->middleware(['auth', 'throttle:6,1'])->name('verification.send');
```

---

## Responses Streamed Responses Event Streams (SSE) Consuming Event Streams
Responses | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/responses.md

#### Consuming Event Streams

Event streams may be consumed using Laravel's `stream` npm package, which provides a convenient API for interacting with Laravel event streams. To get started, install the `@laravel/stream-react`, `@laravel/stream-vue`, or `@laravel/stream-svelte` package:

```shell tab=React
npm install @laravel/stream-react
```

```shell tab=Vue
npm install @laravel/stream-vue
```

```shell tab=Svelte
npm install @laravel/stream-svelte
```

Then, `useEventStream` may be used to consume the event stream. After providing your stream URL, the hook will automatically update the `message` with the concatenated response as messages are returned from your Laravel application:

```jsx tab=React
import { useEventStream } from "@laravel/stream-react";

function App() {
  const { message } = useEventStream("/chat");

  return <div>{message}</div>;
}
```

```vue tab=Vue
<script setup lang="ts">
import { useEventStream } from "@laravel/stream-vue";

const { message } = useEventStream("/chat");
</script>

<template>
  <div>{{ message }}</div>
</template>
```

```svelte tab=Svelte
<script>
import { useEventStream } from "@laravel/stream-svelte";

const eventStream = useEventStream("/chat");
</script>

<div>{$eventStream.message}</div>
```

The second argument given to `useEventStream` is an options object that you may use to customize the stream consumption behavior. The default values for this object are shown below:

```jsx tab=React
import { useEventStream } from "@laravel/stream-react";

function App() {
  const { message } = useEventStream("/stream", {
    eventName: "update",
    onMessage: (message) => {
      //
    },
    onError: (error) => {
      //
    },
    onComplete: () => {
      //
    },
    endSignal: "</stream>",
    glue: " ",
  });

  return <div>{message}</div>;
}
```

```vue tab=Vue
<script setup lang="ts">
import { useEventStream } from "@laravel/stream-vue";

const { message } = useEventStream("/chat", {
  eventName: "update",
  onMessage: (message) => {
    // ...
  },
  onError: (error) => {
    // ...
  },
  onComplete: () => {
    // ...
  },
  endSignal: "</stream>",
  glue: " ",
});
</script>
```

```svelte tab=Svelte
<script>
import { useEventStream } from "@laravel/stream-svelte";

const eventStream = useEventStream("/chat", {
    eventName: "update",
    onMessage: (event) => {
        //
    },
    onError: (error) => {
        //
    },
    onComplete: () => {
        //
    },
    endSignal: "</stream>",
    glue: " ",
    replace: false,
});
</script>
```

Event streams may also be manually consumed via an [EventSource](https://developer.mozilla.org/en-US/docs/Web/API/EventSource) object by your application's frontend. The `eventStream` method will automatically send a `</stream>` update to the event stream when the stream is complete:

```js
const source = new EventSource('/chat');

source.addEventListener('update', (event) => {
    if (event.data === '</stream>') {
        source.close();

        return;
    }

    console.log(event.data);
});
```

To customize the final event that is sent to the event stream, you may provide a `StreamedEvent` instance to the `eventStream` method's `endStreamWith` argument:

```php
return response()->eventStream(function () {
    // ...
}, endStreamWith: new StreamedEvent(event: 'update', data: '</stream>'));
```

---

## Validation Validating Files Validating File Types
Validation | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/validation.md

#### Validating File Types

Even though you only need to specify the extensions when invoking the `types` method, this method actually validates the MIME type of the file by reading the file's contents and guessing its MIME type. A full listing of MIME types and their corresponding extensions may be found at the following location:

[https://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types](https://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types)

---

## Responses Streamed Responses Event Streams (SSE)
Responses | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/responses.md

### Event Streams (SSE)

The `eventStream` method may be used to return a server-sent events (SSE) streamed response using the `text/event-stream` content type. The `eventStream` method accepts a closure which should [yield](https://www.php.net/manual/en/language.generators.overview.php) responses to the stream as the responses become available:

```php
Route::get('/chat', function () {
    return response()->eventStream(function () {
        $stream = OpenAI::client()->chat()->createStreamed(...);

        foreach ($stream as $response) {
            yield $response->choices[0];
        }
    });
});
```

If you would like to customize the name of the event, you may yield an instance of the `StreamedEvent` class:

```php
use Illuminate\Http\StreamedEvent;

yield new StreamedEvent(
    event: 'update',
    data: $response->choices[0],
);
```

---

## Verification Routing The Email Verification Handler
Verification | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/verification.md

### The Email Verification Handler

Next, we need to define a route that will handle requests generated when the user clicks the email verification link that was emailed to them. This route should be named `verification.verify` and be assigned the `auth` and `signed` middlewares:

```php
use Illuminate\Foundation\Auth\EmailVerificationRequest;

Route::get('/email/verify/{id}/{hash}', function (EmailVerificationRequest $request) {
    $request->fulfill();

    return redirect('/home');
})->middleware(['auth', 'signed'])->name('verification.verify');
```

Before moving on, let's take a closer look at this route. First, you'll notice we are using an `EmailVerificationRequest` request type instead of the typical `Illuminate\Http\Request` instance. The `EmailVerificationRequest` is a [form request](/docs/{{version}}/validation#form-request-validation) that is included with Laravel. This request will automatically take care of validating the request's `id` and `hash` parameters.

Next, we can proceed directly to calling the `fulfill` method on the request. This method will call the `markEmailAsVerified` method on the authenticated user and dispatch the `Illuminate\Auth\Events\Verified` event. The `markEmailAsVerified` method is available to the default `App\Models\User` model via the `Illuminate\Foundation\Auth\User` base class. Once the user's email address has been verified, you may redirect them wherever you wish.

---

## Responses Streamed Responses Streamed JSON Responses
Responses | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/responses.md

### Streamed JSON Responses

If you need to stream JSON data incrementally, you may utilize the `streamJson` method. This method is especially useful for large datasets that need to be sent progressively to the browser in a format that can be easily parsed by JavaScript:

```php
use App\Models\User;

Route::get('/users.json', function () {
    return response()->streamJson([
        'users' => User::cursor(),
    ]);
});
```

The `useJsonStream` hook is identical to the [useStream hook](#consuming-streamed-responses) except that it will attempt to parse the data as JSON once it has finished streaming:

```tsx tab=React
import { useJsonStream } from "@laravel/stream-react";

type User = {
    id: number;
    name: string;
    email: string;
};

function App() {
    const { data, send } = useJsonStream<{ users: User[] }>("users");

    const loadUsers = () => {
        send({
            query: "taylor",
        });
    };

    return (
        <div>
            <ul>
                {data?.users.map((user) => (
                    <li>
                        {user.id}: {user.name}
                    </li>
                ))}
            </ul>
            <button onClick={loadUsers}>Load Users</button>
        </div>
    );
}
```

```vue tab=Vue
<script setup lang="ts">
import { useJsonStream } from "@laravel/stream-vue";

type User = {
    id: number;
    name: string;
    email: string;
};

const { data, send } = useJsonStream<{ users: User[] }>("users");

const loadUsers = () => {
    send({
        query: "taylor",
    });
};
</script>

<template>
    <div>
        <ul>
            <li v-for="user in data?.users" :key="user.id">
                {{ user.id }}: {{ user.name }}
            </li>
        </ul>
        <button @click="loadUsers">Load Users</button>
    </div>
</template>
```

```svelte tab=Svelte
<script>
import { useJsonStream } from "@laravel/stream-svelte";

const stream = useJsonStream("users");

const loadUsers = () => {
    stream.send({
        query: "taylor",
    });
};
</script>

<div>
    <ul>
        {#if $stream.data?.users}
            {#each $stream.data.users as user (user.id)}
                <li>{user.id}: {user.name}</li>
            {/each}
        {/if}
    </ul>
    <button onclick={loadUsers}>Load Users</button>
</div>
```

_[Truncated]_


Output:
Wall time: 3.4058 seconds
Output:
[{"type":"text","text":"# Search Results\n\n## Http Tests Testing JSON APIs\nHttp Tests | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/http-tests.md\n\n\n## Testing JSON APIs\n\nLaravel also provides several helpers for testing JSON APIs and their responses. For example, the `json`, `getJson`, `postJson`, `putJson`, `patchJson`, `deleteJson`, and `optionsJson` methods may be used to issue JSON requests with various HTTP verbs. You may also easily pass data and headers to these methods. To get started, let's write a test to make a `POST` request to `/api/user` and assert that the expected JSON data was returned:\n\n```php tab=Pest\n<?php\n\ntest('making an api request', function () {\n    $response = $this->postJson('/api/user', ['name' => 'Sally']);\n\n    $response\n        ->assertStatus(201)\n        ->assertJson([\n            'created' => true,\n        ]);\n});\n```\n\n```php tab=PHPUnit\n<?php\n\nnamespace Tests\\Feature;\n\nuse Tests\\TestCase;\n\nclass ExampleTest extends TestCase\n{\n    /**\n     * A basic functional test example.\n     */\n    public function test_making_an_api_request(): void\n    {\n        $response = $this->postJson('/api/user', ['name' => 'Sally']);\n\n        $response\n            ->assertStatus(201)\n            ->assertJson([\n                'created' => true,\n            ]);\n    }\n}\n```\n\nIn addition, JSON response data may be accessed as array variables on the response, making it convenient for you to inspect the individual values returned within a JSON response:\n\n```php tab=Pest\nexpect($response['created'])->toBeTrue();\n```\n\n```php tab=PHPUnit\n$this->assertTrue($response['created']);\n```\n\n> [!NOTE]\n> The `assertJson` method converts the response to an array to verify that the given array exists within the JSON response returned by the application. So, if there are other properties in the JSON response, this test will still pass as long as the given fragment is present.\n\n---\n\n## Mutation Testing Options & Modifiers `--profile`\nMutation Testing | pestphp/pest@4.x | Source: https://github.com/pestphp/docs/blob/4.x/mutation-testing.md\n\n\n### `--profile`\n\nOutput to standard output the top ten slowest mutations.\n\n```bash\n./vendor/bin/pest --mutate --profile\n```\n\n---\n\n## Vite Working With Blade and Routes Aliases\nVite | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/vite.md\n\n\n### Aliases\n\nIt is common in JavaScript applications to [create aliases](#aliases) to regularly referenced directories. But, you may also create aliases to use in Blade by using the `macro` method on the `Illuminate\\Support\\Facades\\Vite` class. Typically, \"macros\" should be defined within the `boot` method of a [service provider](/docs/{{version}}/providers):\n\n```php\n/**\n * Bootstrap any application services.\n */\npublic function boot(): void\n{\n    Vite::macro('image', fn (string $asset) => $this->asset(\"resources/images/{$asset}\"));\n}\n```\n\nOnce a macro has been defined, it can be invoked within your templates. For example, we can use the `image` macro defined above to reference an asset located at `resources/images/logo.png`:\n\n```blade\n<img src=\"{{ Vite::image('logo.png') }}\" alt=\"Laravel Logo\">\n```\n\n---\n\n## Expectations `toBeWritableFile()`\nExpectations | pestphp/pest@4.x | Source: https://github.com/pestphp/docs/blob/4.x/expectations.md\n\n\n### `toBeWritableFile()`\n\nThis expectation ensures that the string `$value` is a file and that it is writable.\n\n```php\nexpect('/tmp/some-file.tmp')->toBeWritableFile();\n```\n\n---\n\n## Vite Working With Blade and Routes Refreshing on Save\nVite | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/vite.md\n\n\n### Refreshing on Save\n\nWhen your application is built using traditional server-side rendering with Blade, Vite can improve your development workflow by automatically refreshing the browser when you make changes to view files in your application. To get started, you can simply specify the `refresh` option as `true`.\n\n```js\nimport { defineConfig } from 'vite';\nimport laravel from 'laravel-vite-plugin';\n\nexport default defineConfig({\n    plugins: [\n        laravel({\n            // ...\n            refresh: true,\n        }),\n    ],\n});\n```\n\nWhen the `refresh` option is `true`, saving files in the following directories will trigger the browser to perform a full page refresh while you are running `npm run dev`:\n\n- `app/Livewire/**`\n- `app/View/Components/**`\n- `lang/**`\n- `resources/lang/**`\n- `resources/views/**`\n- `routes/**`\n\nWatching the `routes/**` directory is useful if you are utilizing [Ziggy](https://github.com/tighten/ziggy) to generate route links within your application's frontend.\n\nIf these default paths do not suit your needs, you can specify your own list of paths to watch:\n\n```js\nimport { defineConfig } from 'vite';\nimport laravel from 'laravel-vite-plugin';\n\nexport default defineConfig({\n    plugins: [\n        laravel({\n            // ...\n            refresh: ['resources/views/**'],\n        }),\n    ],\n});\n```\n\nUnder the hood, the Laravel Vite plugin uses the [vite-plugin-full-reload](https://github.com/ElMassimo/vite-plugin-full-reload) package, which offers some advanced configuration options to fine-tune this feature's behavior. If you need this level of customization, you may provide a `config` definition:\n\n```js\nimport { defineConfig } from 'vite';\nimport laravel from 'laravel-vite-plugin';\n\nexport default defineConfig({\n    plugins: [\n        laravel({\n            // ...\n            refresh: [{\n                paths: ['path/to/watch/**'],\n                config: { delay: 300 }\n            }],\n        }),\n    ],\n});\n```\n\n---\n\n## Validation Validation Quickstart Validation Error Response Format\nValidation | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/validation.md\n\n\n### Validation Error Response Format\n\nWhen your application throws a `Illuminate\\Validation\\ValidationException` exception and the incoming HTTP request is expecting a JSON response, Laravel will automatically format the error messages for you and return a `422 Unprocessable Entity` HTTP response.\n\nBelow, you can review an example of the JSON response format for validation errors. Note that nested error keys are flattened into \"dot\" notation format:\n\n```json\n{\n    \"message\": \"The team name must be a string. (and 4 more errors)\",\n    \"errors\": {\n        \"team_name\": [\n            \"The team name must be a string.\",\n            \"The team name must be at least 1 characters.\"\n        ],\n        \"authorization.role\": [\n            \"The selected authorization.role is invalid.\"\n        ],\n        \"users.0.email\": [\n            \"The users.0.email field is required.\"\n        ],\n        \"users.2.email\": [\n            \"The users.2.email must be a valid email address.\"\n        ]\n    }\n}\n```\n\n---\n\n## Requests Files Retrieving Uploaded Files Validating Successful Uploads\nRequests | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/requests.md\n\n\n#### Validating Successful Uploads\n\nIn addition to checking if the file is present, you may verify that there were no problems uploading the file via the `isValid` method:\n\n```php\nif ($request->file('photo')->isValid()) {\n    // ...\n}\n```\n\n---\n\n## Routing Route Model Binding Implicit Binding Custom Keys and Scoping\nRouting | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/routing.md\n\n\n#### Custom Keys and Scoping\n\nWhen implicitly binding multiple Eloquent models in a single route definition, you may wish to scope the second Eloquent model such that it must be a child of the previous Eloquent model. For example, consider this route definition that retrieves a blog post by slug for a specific user:\n\n```php\nuse App\\Models\\Post;\nuse App\\Models\\User;\n\nRoute::get('/users/{user}/posts/{post:slug}', function (User $user, Post $post) {\n    return $post;\n});\n```\n\nWhen using a custom keyed implicit binding as a nested route parameter, Laravel will automatically scope the query to retrieve the nested model by its parent using conventions to guess the relationship name on the parent. In this case, it will be assumed that the `User` model has a relationship named `posts` (the plural form of the route parameter name) which can be used to retrieve the `Post` model.\n\nIf you wish, you may instruct Laravel to scope \"child\" bindings even when a custom key is not provided. To do so, you may invoke the `scopeBindings` method when defining your route:\n\n```php\nuse App\\Models\\Post;\nuse App\\Models\\User;\n\nRoute::get('/users/{user}/posts/{post}', function (User $user, Post $post) {\n    return $post;\n})->scopeBindings();\n```\n\nOr, you may instruct an entire group of route definitions to use scoped bindings:\n\n```php\nRoute::scopeBindings()->group(function () {\n    Route::get('/users/{user}/posts/{post}', function (User $user, Post $post) {\n        return $post;\n    });\n});\n```\n\nSimilarly, you may explicitly instruct Laravel to not scope bindings by invoking the `withoutScopedBindings` method:\n\n```php\nRoute::get('/users/{user}/posts/{post:slug}', function (User $user, Post $post) {\n    return $post;\n})->withoutScopedBindings();\n```\n\n---\n\n## Expectations `toBeReadableFile()`\nExpectations | pestphp/pest@4.x | Source: https://github.com/pestphp/docs/blob/4.x/expectations.md\n\n\n### `toBeReadableFile()`\n\nThis expectation ensures that the string `$value` is a file and that it is readable.\n\n```php\nexpect('/tmp/some-file.tmp')->toBeReadableFile();\n```\n\n---\n\n## Vite Working With Blade and Routes Processing Static Assets With Vite\nVite | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/vite.md\n\n\n### Processing Static Assets With Vite\n\nWhen referencing assets in your JavaScript or CSS, Vite automatically processes and versions them. In addition, when building Blade based applications, Vite can also process and version static assets that you reference solely in Blade templates.\n\nHowever, in order to accomplish this, you need to make Vite aware of your assets by importing the static assets into the application's entry point. For example, if you want to process and version all images stored in `resources/images` and all fonts stored in `resources/fonts`, you should add the following in your application's `resources/js/app.js` entry point:\n\n```js\nimport.meta.glob([\n  '../images/**',\n  '../fonts/**',\n]);\n```\n\nThese assets will now be processed by Vite when running `npm run build`. You can then reference these assets in Blade templates using the `Vite::asset` method, which will return the versioned URL for a given asset:\n\n```blade\n<img src=\"{{ Vite::asset('resources/images/logo.png') }}\">\n```\n\n---\n\n## Routing Rate Limiting Defining Rate Limiters Response-Based Rate Limiting\nRouting | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/routing.md\n\n\n#### Response-Based Rate Limiting\n\nIn addition to rate limiting incoming requests, Laravel allows you to rate limit based on the response using the `after` method. This is useful when you only want to count certain responses toward the rate limit, such as validation errors, 404 responses, or other specific HTTP status codes.\n\nThe `after` method accepts a closure that receives the response and should return `true` if the response should be counted toward the rate limit, or `false` if it should be ignored. This is particularly useful for preventing enumeration attacks by limiting consecutive 404 responses, or allowing users to retry requests that fail validation without exhausting their rate limit on an endpoint that should only throttle successful operations:\n\n```php\nuse Illuminate\\Cache\\RateLimiting\\Limit;\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Support\\Facades\\RateLimiter;\nuse Symfony\\Component\\HttpFoundation\\Response;\n\nRateLimiter::for('resource-not-found', function (Request $request) {\n    return Limit::perMinute(10)\n        ->by($request->user()?->id ?: $request->ip())\n        ->after(function (Response $response) {\n            // Only count 404 responses toward the rate limit to prevent enumeration...\n            return $response->status() === 404;\n        });\n});\n```\n\n---\n\n## Validation Validating Files\nValidation | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/validation.md\n\n\n## Validating Files\n\nLaravel provides a variety of validation rules that may be used to validate uploaded files, such as `mimes`, `image`, `min`, and `max`. While you are free to specify these rules individually when validating files, Laravel also offers a fluent file validation rule builder that you may find convenient:\n\n```php\nuse Illuminate\\Support\\Facades\\Validator;\nuse Illuminate\\Validation\\Rules\\File;\n\nValidator::validate($input, [\n    'attachment' => [\n        'required',\n        File::types(['mp3', 'wav'])\n            ->min(1024)\n            ->max(12 * 1024),\n    ],\n]);\n```\n\n---\n\n## Vite Working With Blade and Routes\nVite | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/vite.md\n\n\n## Working With Blade and Routes\n\n---\n\n## Expectations `toBeFile()`\nExpectations | pestphp/pest@4.x | Source: https://github.com/pestphp/docs/blob/4.x/expectations.md\n\n\n### `toBeFile()`\n\nThis expectation ensures that the string `$value` is an existing file.\n\n```php\nexpect('/tmp/some-file.tmp')->toBeFile();\n```\n\n---\n\n## Responses Streamed Responses Streamed Downloads\nResponses | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/responses.md\n\n\n### Streamed Downloads\n\nSometimes you may wish to turn the string response of a given operation into a downloadable response without having to write the contents of the operation to disk. You may use the `streamDownload` method in this scenario. This method accepts a callback, filename, and an optional array of headers as its arguments:\n\n```php\nuse App\\Services\\GitHub;\n\nreturn response()->streamDownload(function () {\n    echo GitHub::api('repo')\n        ->contents()\n        ->readme('laravel', 'laravel')['contents'];\n}, 'laravel-readme.md');\n```\n\n---\n\n## Routing Route Model Binding\nRouting | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/routing.md\n\n\n## Route Model Binding\n\nWhen injecting a model ID to a route or controller action, you will often query the database to retrieve the model that corresponds to that ID. Laravel route model binding provides a convenient way to automatically inject the model instances directly into your routes. For example, instead of injecting a user's ID, you can inject the entire `User` model instance that matches the given ID.\n\n---\n\n## Validation Available Validation Rules file\nValidation | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/validation.md\n\n\n#### file\n\nThe field under validation must be a successfully uploaded file.\n\n---\n\n## Arch Testing Expectations `toHaveFileSystemPermissions()`\nArch Testing | pestphp/pest@4.x | Source: https://github.com/pestphp/docs/blob/4.x/arch-testing.md\n\n\n### `toHaveFileSystemPermissions()`\n\nThe `toHaveFileSystemPermissions()` method may be used to ensure that all files within a given namespace have specific file system permissions.\n\n```php\narch('app')\n    ->expect('App')\n    ->not->toHaveFileSystemPermissions('0777');\n```\n\n---\n\n## Http Tests Available Assertions Response Assertions assertJson\nHttp Tests | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/http-tests.md\n\n\n#### assertJson\n\nAssert that the response contains the given JSON data:\n\n```php\n$response->assertJson(array $data, $strict = false);\n```\n\nThe `assertJson` method converts the response to an array to verify that the given array exists within the JSON response returned by the application. So, if there are other properties in the JSON response, this test will still pass as long as the given fragment is present.\n\n---\n\n## Validation Validating Files Validating Image Dimensions\nValidation | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/validation.md\n\n\n#### Validating Image Dimensions\n\nYou may also validate the dimensions of an image. For example, to validate that an uploaded image is at least 1000 pixels wide and 500 pixels tall, you may use the `dimensions` rule:\n\n```php\nuse Illuminate\\Validation\\Rule;\nuse Illuminate\\Validation\\Rules\\File;\n\nFile::image()->dimensions(\n    Rule::dimensions()\n        ->maxWidth(1000)\n        ->maxHeight(500)\n)\n```\n\n> [!NOTE]\n> More information regarding validating image dimensions may be found in the [dimension rule documentation](#rule-dimensions).\n\n---\n\n## Responses Other Response Types JSON Responses\nResponses | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/responses.md\n\n\n### JSON Responses\n\nThe `json` method will automatically set the `Content-Type` header to `application/json`, as well as convert the given array to JSON using the `json_encode` PHP function:\n\n```php\nreturn response()->json([\n    'name' => 'Abigail',\n    'state' => 'CA',\n]);\n```\n\nIf you would like to create a JSONP response, you may use the `json` method in combination with the `withCallback` method:\n\n```php\nreturn response()\n    ->json(['name' => 'Abigail', 'state' => 'CA'])\n    ->withCallback($request->input('callback'));\n```\n\n---\n\n## Verification Routing Protecting Routes\nVerification | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/verification.md\n\n\n### Protecting Routes\n\n[Route middleware](/docs/{{version}}/middleware) may be used to only allow verified users to access a given route. Laravel includes a `verified` [middleware alias](/docs/{{version}}/middleware#middleware-aliases), which is an alias for the `Illuminate\\Auth\\Middleware\\EnsureEmailIsVerified` middleware class. Since this alias is already automatically registered by Laravel, all you need to do is attach the `verified` middleware to a route definition. Typically, this middleware is paired with the `auth` middleware:\n\n```php\nRoute::get('/profile', function () {\n    // Only verified users may access this route...\n})->middleware(['auth', 'verified']);\n```\n\nIf an unverified user attempts to access a route that has been assigned this middleware, they will automatically be redirected to the `verification.notice` [named route](/docs/{{version}}/routing#named-routes).\n\n---\n\n## Responses Response Macros\nResponses | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/responses.md\n\n\n## Response Macros\n\nIf you would like to define a custom response that you can re-use in a variety of your routes and controllers, you may use the `macro` method on the `Response` facade. Typically, you should call this method from the `boot` method of one of your application's [service providers](/docs/{{version}}/providers), such as the `App\\Providers\\AppServiceProvider` service provider:\n\n```php\n<?php\n\nnamespace App\\Providers;\n\nuse Illuminate\\Support\\Facades\\Response;\nuse Illuminate\\Support\\ServiceProvider;\n\nclass AppServiceProvider extends ServiceProvider\n{\n    /**\n     * Bootstrap any application services.\n     */\n    public function boot(): void\n    {\n        Response::macro('caps', function (string $value) {\n            return Response::make(strtoupper($value));\n        });\n    }\n}\n```\n\nThe `macro` function accepts a name as its first argument and a closure as its second argument. The macro's closure will be executed when calling the macro name from a `ResponseFactory` implementation or the `response` helper:\n\n```php\nreturn response()->caps('foo');\n```\n\n---\n\n## Validation Validating Files Validating Image Files\nValidation | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/validation.md\n\n\n#### Validating Image Files\n\nIf your application accepts images uploaded by your users, you may use the `File` rule's `image` constructor method to ensure that the file under validation is an image (jpg, jpeg, png, bmp, gif, or webp).\n\nIn addition, the `dimensions` rule may be used to limit the dimensions of the image:\n\n```php\nuse Illuminate\\Support\\Facades\\Validator;\nuse Illuminate\\Validation\\Rule;\nuse Illuminate\\Validation\\Rules\\File;\n\nValidator::validate($input, [\n    'photo' => [\n        'required',\n        File::image()\n            ->min(1024)\n            ->max(12 * 1024)\n            ->dimensions(Rule::dimensions()->maxWidth(1000)->maxHeight(500)),\n    ],\n]);\n```\n\n> [!NOTE]\n> More information regarding validating image dimensions may be found in the [dimension rule documentation](#rule-dimensions).\n\n> [!WARNING]\n> By default, the `image` rule does not allow SVG files due to the possibility of XSS vulnerabilities. If you need to allow SVG files, you may pass `allowSvg: true` to the `image` rule: `File::image(allowSvg: true)`.\n\n---\n\n## Validation Validating Files Validating File Sizes\nValidation | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/validation.md\n\n\n#### Validating File Sizes\n\nFor convenience, minimum and maximum file sizes may be specified as a string with a suffix indicating the file size units. The `kb`, `mb`, `gb`, and `tb` suffixes are supported:\n\n```php\nFile::types(['mp3', 'wav'])\n    ->min('1kb')\n    ->max('10mb');\n```\n\n---\n\n## Routing Route Model Binding Explicit Binding Customizing the Resolution Logic\nRouting | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/routing.md\n\n\n#### Customizing the Resolution Logic\n\nIf you wish to define your own model binding resolution logic, you may use the `Route::bind` method. The closure you pass to the `bind` method will receive the value of the URI segment and should return the instance of the class that should be injected into the route. Again, this customization should take place in the `boot` method of your application's `AppServiceProvider`:\n\n```php\nuse App\\Models\\User;\nuse Illuminate\\Support\\Facades\\Route;\n\n/**\n * Bootstrap any application services.\n */\npublic function boot(): void\n{\n    Route::bind('user', function (string $value) {\n        return User::where('name', $value)->firstOrFail();\n    });\n}\n```\n\nAlternatively, you may override the `resolveRouteBinding` method on your Eloquent model. This method will receive the value of the URI segment and should return the instance of the class that should be injected into the route:\n\n```php\n/**\n * Retrieve the model for a bound value.\n *\n * @param  mixed  $value\n * @param  string|null  $field\n * @return \\Illuminate\\Database\\Eloquent\\Model|null\n */\npublic function resolveRouteBinding($value, $field = null)\n{\n    return $this->where('name', $value)->firstOrFail();\n}\n```\n\nIf a route is utilizing [implicit binding scoping](#implicit-model-binding-scoping), the `resolveChildRouteBinding` method will be used to resolve the child binding of the parent model:\n\n```php\n/**\n * Retrieve the child model for a bound value.\n *\n * @param  string  $childType\n * @param  mixed  $value\n * @param  string|null  $field\n * @return \\Illuminate\\Database\\Eloquent\\Model|null\n */\npublic function resolveChildRouteBinding($childType, $value, $field)\n{\n    return parent::resolveChildRouteBinding($childType, $value, $field);\n}\n```\n\n---\n\n## Verification Routing Resending the Verification Email\nVerification | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/verification.md\n\n\n### Resending the Verification Email\n\nSometimes a user may misplace or accidentally delete the email address verification email. To accommodate this, you may wish to define a route to allow the user to request that the verification email be resent. You may then make a request to this route by placing a simple form submission button within your [verification notice view](#the-email-verification-notice):\n\n```php\nuse Illuminate\\Http\\Request;\n\nRoute::post('/email/verification-notification', function (Request $request) {\n    $request->user()->sendEmailVerificationNotification();\n\n    return back()->with('message', 'Verification link sent!');\n})->middleware(['auth', 'throttle:6,1'])->name('verification.send');\n```\n\n---\n\n## Responses Streamed Responses Event Streams (SSE) Consuming Event Streams\nResponses | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/responses.md\n\n\n#### Consuming Event Streams\n\nEvent streams may be consumed using Laravel's `stream` npm package, which provides a convenient API for interacting with Laravel event streams. To get started, install the `@laravel/stream-react`, `@laravel/stream-vue`, or `@laravel/stream-svelte` package:\n\n```shell tab=React\nnpm install @laravel/stream-react\n```\n\n```shell tab=Vue\nnpm install @laravel/stream-vue\n```\n\n```shell tab=Svelte\nnpm install @laravel/stream-svelte\n```\n\nThen, `useEventStream` may be used to consume the event stream. After providing your stream URL, the hook will automatically update the `message` with the concatenated response as messages are returned from your Laravel application:\n\n```jsx tab=React\nimport { useEventStream } from \"@laravel/stream-react\";\n\nfunction App() {\n  const { message } = useEventStream(\"/chat\");\n\n  return <div>{message}</div>;\n}\n```\n\n```vue tab=Vue\n<script setup lang=\"ts\">\nimport { useEventStream } from \"@laravel/stream-vue\";\n\nconst { message } = useEventStream(\"/chat\");\n</script>\n\n<template>\n  <div>{{ message }}</div>\n</template>\n```\n\n```svelte tab=Svelte\n<script>\nimport { useEventStream } from \"@laravel/stream-svelte\";\n\nconst eventStream = useEventStream(\"/chat\");\n</script>\n\n<div>{$eventStream.message}</div>\n```\n\nThe second argument given to `useEventStream` is an options object that you may use to customize the stream consumption behavior. The default values for this object are shown below:\n\n```jsx tab=React\nimport { useEventStream } from \"@laravel/stream-react\";\n\nfunction App() {\n  const { message } = useEventStream(\"/stream\", {\n    eventName: \"update\",\n    onMessage: (message) => {\n      //\n    },\n    onError: (error) => {\n      //\n    },\n    onComplete: () => {\n      //\n    },\n    endSignal: \"</stream>\",\n    glue: \" \",\n  });\n\n  return <div>{message}</div>;\n}\n```\n\n```vue tab=Vue\n<script setup lang=\"ts\">\nimport { useEventStream } from \"@laravel/stream-vue\";\n\nconst { message } = useEventStream(\"/chat\", {\n  eventName: \"update\",\n  onMessage: (message) => {\n    // ...\n  },\n  onError: (error) => {\n    // ...\n  },\n  onComplete: () => {\n    // ...\n  },\n  endSignal: \"</stream>\",\n  glue: \" \",\n});\n</script>\n```\n\n```svelte tab=Svelte\n<script>\nimport { useEventStream } from \"@laravel/stream-svelte\";\n\nconst eventStream = useEventStream(\"/chat\", {\n    eventName: \"update\",\n    onMessage: (event) => {\n        //\n    },\n    onError: (error) => {\n        //\n    },\n    onComplete: () => {\n        //\n    },\n    endSignal: \"</stream>\",\n    glue: \" \",\n    replace: false,\n});\n</script>\n```\n\nEvent streams may also be manually consumed via an [EventSource](https://developer.mozilla.org/en-US/docs/Web/API/EventSource) object by your application's frontend. The `eventStream` method will automatically send a `</stream>` update to the event stream when the stream is complete:\n\n```js\nconst source = new EventSource('/chat');\n\nsource.addEventListener('update', (event) => {\n    if (event.data === '</stream>') {\n        source.close();\n\n        return;\n    }\n\n    console.log(event.data);\n});\n```\n\nTo customize the final event that is sent to the event stream, you may provide a `StreamedEvent` instance to the `eventStream` method's `endStreamWith` argument:\n\n```php\nreturn response()->eventStream(function () {\n    // ...\n}, endStreamWith: new StreamedEvent(event: 'update', data: '</stream>'));\n```\n\n---\n\n## Validation Validating Files Validating File Types\nValidation | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/validation.md\n\n\n#### Validating File Types\n\nEven though you only need to specify the extensions when invoking the `types` method, this method actually validates the MIME type of the file by reading the file's contents and guessing its MIME type. A full listing of MIME types and their corresponding extensions may be found at the following location:\n\n[https://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types](https://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types)\n\n---\n\n## Responses Streamed Responses Event Streams (SSE)\nResponses | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/responses.md\n\n\n### Event Streams (SSE)\n\nThe `eventStream` method may be used to return a server-sent events (SSE) streamed response using the `text/event-stream` content type. The `eventStream` method accepts a closure which should [yield](https://www.php.net/manual/en/language.generators.overview.php) responses to the stream as the responses become available:\n\n```php\nRoute::get('/chat', function () {\n    return response()->eventStream(function () {\n        $stream = OpenAI::client()->chat()->createStreamed(...);\n\n        foreach ($stream as $response) {\n            yield $response->choices[0];\n        }\n    });\n});\n```\n\nIf you would like to customize the name of the event, you may yield an instance of the `StreamedEvent` class:\n\n```php\nuse Illuminate\\Http\\StreamedEvent;\n\nyield new StreamedEvent(\n    event: 'update',\n    data: $response->choices[0],\n);\n```\n\n---\n\n## Verification Routing The Email Verification Handler\nVerification | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/verification.md\n\n\n### The Email Verification Handler\n\nNext, we need to define a route that will handle requests generated when the user clicks the email verification link that was emailed to them. This route should be named `verification.verify` and be assigned the `auth` and `signed` middlewares:\n\n```php\nuse Illuminate\\Foundation\\Auth\\EmailVerificationRequest;\n\nRoute::get('/email/verify/{id}/{hash}', function (EmailVerificationRequest $request) {\n    $request->fulfill();\n\n    return redirect('/home');\n})->middleware(['auth', 'signed'])->name('verification.verify');\n```\n\nBefore moving on, let's take a closer look at this route. First, you'll notice we are using an `EmailVerificationRequest` request type instead of the typical `Illuminate\\Http\\Request` instance. The `EmailVerificationRequest` is a [form request](/docs/{{version}}/validation#form-request-validation) that is included with Laravel. This request will automatically take care of validating the request's `id` and `hash` parameters.\n\nNext, we can proceed directly to calling the `fulfill` method on the request. This method will call the `markEmailAsVerified` method on the authenticated user and dispatch the `Illuminate\\Auth\\Events\\Verified` event. The `markEmailAsVerified` method is available to the default `App\\Models\\User` model via the `Illuminate\\Foundation\\Auth\\User` base class. Once the user's email address has been verified, you may redirect them wherever you wish.\n\n---\n\n## Responses Streamed Responses Streamed JSON Responses\nResponses | laravel/framework@12.x | Source: https://github.com/laravel/docs/blob/12.x/responses.md\n\n\n### Streamed JSON Responses\n\nIf you need to stream JSON data incrementally, you may utilize the `streamJson` method. This method is especially useful for large datasets that need to be sent progressively to the browser in a format that can be easily parsed by JavaScript:\n\n```php\nuse App\\Models\\User;\n\nRoute::get('/users.json', function () {\n    return response()->streamJson([\n        'users' => User::cursor(),\n    ]);\n});\n```\n\nThe `useJsonStream` hook is identical to the [useStream hook](#consuming-streamed-responses) except that it will attempt to parse the data as JSON once it has finished streaming:\n\n```tsx tab=React\nimport { useJsonStream } from \"@laravel/stream-react\";\n\ntype User = {\n    id: number;\n    name: string;\n    email: string;\n};\n\nfunction App() {\n    const { data, send } = useJsonStream<{ users: User[] }>(\"users\");\n\n    const loadUsers = () => {\n        send({\n            query: \"taylor\",\n        });\n    };\n\n    return (\n        <div>\n            <ul>\n                {data?.users.map((user) => (\n                    <li>\n                        {user.id}: {user.name}\n                    </li>\n                ))}\n            </ul>\n            <button onClick={loadUsers}>Load Users</button>\n        </div>\n    );\n}\n```\n\n```vue tab=Vue\n<script setup lang=\"ts\">\nimport { useJsonStream } from \"@laravel/stream-vue\";\n\ntype User = {\n    id: number;\n    name: string;\n    email: string;\n};\n\nconst { data, send } = useJsonStream<{ users: User[] }>(\"users\");\n\nconst loadUsers = () => {\n    send({\n        query: \"taylor\",\n    });\n};\n</script>\n\n<template>\n    <div>\n        <ul>\n            <li v-for=\"user in data?.users\" :key=\"user.id\">\n                {{ user.id }}: {{ user.name }}\n            </li>\n        </ul>\n        <button @click=\"loadUsers\">Load Users</button>\n    </div>\n</template>\n```\n\n```svelte tab=Svelte\n<script>\nimport { useJsonStream } from \"@laravel/stream-svelte\";\n\nconst stream = useJsonStream(\"users\");\n\nconst loadUsers = () => {\n    stream.send({\n        query: \"taylor\",\n    });\n};\n</script>\n\n<div>\n    <ul>\n        {#if $stream.data?.users}\n            {#each $stream.data.users as user (user.id)}\n                <li>{user.id}: {user.name}</li>\n            {/each}\n        {/if}\n    </ul>\n    <button onclick={loadUsers}>Load Users</button>\n</div>\n```\n\n_[Truncated]_"}]


sed -n '2498,2595p' specs/02-API-ROUTES.md

| POST | `/api/admin/v1/stores/{storeId}/themes` | `write-themes` | Upload or install a new theme | `api.admin` (60/min) |
| POST | `/api/admin/v1/stores/{storeId}/themes/{themeId}/publish` | `write-themes` | Publish a theme (make active) | `api.admin` (60/min) |
| PUT | `/api/admin/v1/stores/{storeId}/themes/{themeId}/settings` | `write-themes` | Update theme customization settings | `api.admin` (60/min) |

#### POST /api/admin/v1/stores/{storeId}/themes

Upload or install a new theme.

**Request body (multipart/form-data):**

| Field | Type | Required | Validation |
|-------|------|----------|------------|
| `file` | file | Yes | ZIP archive. Max: 50MB. Must contain valid theme structure. |
| `name` | string | No | Max: 255. Defaults to theme manifest name. |

**Success response:** `201 Created`

```json
{
  "data": {
    "id": 1,
    "store_id": 1,
    "name": "Dawn",
    "version": "1.0.0",
    "status": "draft",
    "published_at": null,
    "created_at": "2026-02-11T10:00:00Z"
  }
}
```

**Error responses:**
- `403` - Insufficient permissions
- `422` - Invalid theme archive (missing required templates, invalid manifest)

---

#### POST /api/admin/v1/stores/{storeId}/themes/{themeId}/publish

Publish a theme, making it the active storefront theme. The previously published theme transitions to `draft`.

**Success response:** `200 OK`

```json
{
  "data": {
    "id": 1,
    "status": "published",
    "published_at": "2026-02-11T10:05:00Z"
  }
}
```

**Error responses:**
- `403` - Insufficient permissions
- `404` - Theme not found
- `422` - Theme has validation errors preventing publish

---

#### PUT /api/admin/v1/stores/{storeId}/themes/{themeId}/settings

Update theme settings (customization values).

**Request body:**

```json
{
  "settings_json": {
    "colors": {
      "primary": "#1a73e8",
      "secondary": "#fbbc04",
      "background": "#ffffff",
      "text": "#202124"
    },
    "typography": {
      "heading_font": "Inter",
      "body_font": "Inter"
    },
    "header": {
      "show_announcement_bar": true,
      "announcement_text": "Free shipping on orders over 50 EUR!"
    }
  }
}
```

| Field | Type | Required | Validation |
|-------|------|----------|------------|
| `settings_json` | object | Yes | Must conform to the theme's settings schema. |

**Success response:** `200 OK`

**Error responses:**
- `403` - Insufficient permissions
- `404` - Theme not found
- `422` - Settings do not conform to theme schema


Output:
Chunk ID: e0fc25
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 619
Output:
| POST | `/api/admin/v1/stores/{storeId}/themes` | `write-themes` | Upload or install a new theme | `api.admin` (60/min) |
| POST | `/api/admin/v1/stores/{storeId}/themes/{themeId}/publish` | `write-themes` | Publish a theme (make active) | `api.admin` (60/min) |
| PUT | `/api/admin/v1/stores/{storeId}/themes/{themeId}/settings` | `write-themes` | Update theme customization settings | `api.admin` (60/min) |

#### POST /api/admin/v1/stores/{storeId}/themes

Upload or install a new theme.

**Request body (multipart/form-data):**

| Field | Type | Required | Validation |
|-------|------|----------|------------|
| `file` | file | Yes | ZIP archive. Max: 50MB. Must contain valid theme structure. |
| `name` | string | No | Max: 255. Defaults to theme manifest name. |

**Success response:** `201 Created`

```json
{
  "data": {
    "id": 1,
    "store_id": 1,
    "name": "Dawn",
    "version": "1.0.0",
    "status": "draft",
    "published_at": null,
    "created_at": "2026-02-11T10:00:00Z"
  }
}
```

**Error responses:**
- `403` - Insufficient permissions
- `422` - Invalid theme archive (missing required templates, invalid manifest)

---

#### POST /api/admin/v1/stores/{storeId}/themes/{themeId}/publish

Publish a theme, making it the active storefront theme. The previously published theme transitions to `draft`.

**Success response:** `200 OK`

```json
{
  "data": {
    "id": 1,
    "status": "published",
    "published_at": "2026-02-11T10:05:00Z"
  }
}
```

**Error responses:**
- `403` - Insufficient permissions
- `404` - Theme not found
- `422` - Theme has validation errors preventing publish

---

#### PUT /api/admin/v1/stores/{storeId}/themes/{themeId}/settings

Update theme settings (customization values).

**Request body:**

```json
{
  "settings_json": {
    "colors": {
      "primary": "#1a73e8",
      "secondary": "#fbbc04",
      "background": "#ffffff",
      "text": "#202124"
    },
    "typography": {
      "heading_font": "Inter",
      "body_font": "Inter"
    },
    "header": {
      "show_announcement_bar": true,
      "announcement_text": "Free shipping on orders over 50 EUR!"
    }
  }
}
```

| Field | Type | Required | Validation |
|-------|------|----------|------------|
| `settings_json` | object | Yes | Must conform to the theme's settings schema. |

**Success response:** `200 OK`

**Error responses:**
- `403` - Insufficient permissions
- `404` - Theme not found
- `422` - Settings do not conform to theme schema


/bin/zsh
-lc
sed -n '1,260p' app/Models/Theme.php

<?php

namespace App\Models;

use App\Enums\ThemeStatus;
use App\Models\Concerns\BelongsToStore;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;

class Theme extends Model
{
    /** @use HasFactory<\Database\Factories\ThemeFactory> */
    use BelongsToStore, HasFactory;

    /**
     * @var list<string>
     */
    protected $fillable = [
        'store_id',
        'name',
        'version',
        'status',
        'published_at',
    ];

    /**
     * @var array<string, mixed>
     */
    protected $attributes = [
        'status' => 'draft',
    ];

    /**
     * @return BelongsTo<Store, $this>
     */
    public function store(): BelongsTo
    {
        return $this->belongsTo(Store::class);
    }

    /**
     * @return HasMany<ThemeFile, $this>
     */
    public function files(): HasMany
    {
        return $this->hasMany(ThemeFile::class)->orderBy('path');
    }

    /**
     * @return HasOne<ThemeSettings, $this>
     */
    public function settings(): HasOne
    {
        return $this->hasOne(ThemeSettings::class);
    }

    public function isPublished(): bool
    {
        return $this->status === ThemeStatus::Published && $this->published_at !== null;
    }

    /**
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'status' => ThemeStatus::class,
            'published_at' => 'datetime',
        ];
    }
}


sed -n '1,220p' app/Models/ThemeSettings.php

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class ThemeSettings extends Model
{
    /** @use HasFactory<\Database\Factories\ThemeSettingsFactory> */
    use HasFactory;

    public $incrementing = false;

    public const CREATED_AT = null;

    protected $primaryKey = 'theme_id';

    /**
     * @var list<string>
     */
    protected $fillable = [
        'theme_id',
        'settings_json',
    ];

    protected static function booted(): void
    {
        static::addGlobalScope('current_store', function (Builder $builder): void {
            if (! app()->bound('current_store')) {
                return;
            }

            $store = app('current_store');

            if (! $store instanceof Store) {
                return;
            }

            $builder->whereHas('theme', function (Builder $query) use ($store): void {
                $query
                    ->withoutGlobalScopes()
                    ->where('store_id', $store->getKey());
            });
        });
    }

    /**
     * @return BelongsTo<Theme, $this>
     */
    public function theme(): BelongsTo
    {
        return $this->belongsTo(Theme::class);
    }

    /**
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'settings_json' => 'array',
        ];
    }
}


Output:
Chunk ID: 045365
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 405
Output:
<?php

namespace App\Models;

use App\Enums\ThemeStatus;
use App\Models\Concerns\BelongsToStore;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;

class Theme extends Model
{
    /** @use HasFactory<\Database\Factories\ThemeFactory> */
    use BelongsToStore, HasFactory;

    /**
     * @var list<string>
     */
    protected $fillable = [
        'store_id',
        'name',
        'version',
        'status',
        'published_at',
    ];

    /**
     * @var array<string, mixed>
     */
    protected $attributes = [
        'status' => 'draft',
    ];

    /**
     * @return BelongsTo<Store, $this>
     */
    public function store(): BelongsTo
    {
        return $this->belongsTo(Store::class);
    }

    /**
     * @return HasMany<ThemeFile, $this>
     */
    public function files(): HasMany
    {
        return $this->hasMany(ThemeFile::class)->orderBy('path');
    }

    /**
     * @return HasOne<ThemeSettings, $this>
     */
    public function settings(): HasOne
    {
        return $this->hasOne(ThemeSettings::class);
    }

    public function isPublished(): bool
    {
        return $this->status === ThemeStatus::Published && $this->published_at !== null;
    }

    /**
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'status' => ThemeStatus::class,
            'published_at' => 'datetime',
        ];
    }
}


Output:
Chunk ID: 33857d
Wall time: 0.0000 seconds
Process exited with code 0
Original token count: 378
Output:
<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;

class ThemeSettings extends Model
{
    /** @use HasFactory<\Database\Factories\ThemeSettingsFactory> */
    use HasFactory;

    public $incrementing = false;

    public const CREATED_AT = null;

    protected $primaryKey = 'theme_id';

    /**
     * @var list<string>
     */
    protected $fillable = [
        'theme_id',
        'settings_json',
    ];

    protected static function booted(): void
    {
        static::addGlobalScope('current_store', function (Builder $builder): void {
            if (! app()->bound('current_store')) {
                return;
            }

            $store = app('current_store');

            if (! $store instanceof Store) {
                return;
            }

            $builder->whereHas('theme', function (Builder $query) use ($store): void {
                $query
                    ->withoutGlobalScopes()
                    ->where('store_id', $store->getKey());
            });
        });
    }

    /**
     * @return BelongsTo<Theme, $this>
     */
    public function theme(): BelongsTo
    {
        return $this->belongsTo(Theme::class);
    }

    /**
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'settings_json' => 'array',
        ];
    }
}


rg -n "ThemeSettingsService|publish|Theme::|themes" app/Livewire app/Services database tests -g '*.php'

rg -n 'ThemeSettingsService|publish|Theme::|themes' app/Livewire app/Services database tests -g '*.php'

Question: ThemeSettingsService|publish|Theme::|themes

database/factories/PageFactory.php:30:            'published_at' => null,
database/factories/PageFactory.php:34:    public function published(): static
database/factories/PageFactory.php:38:            'published_at' => now(),
database/factories/PageFactory.php:46:            'published_at' => now()->subDay(),
app/Services/SearchService.php:254:            ->whereNotNull('products.published_at');
app/Services/SearchService.php:345:                ->orderByDesc('products.published_at')
app/Services/SearchService.php:349:                ->orderByDesc('products.published_at')
app/Services/SearchService.php:353:                : $builder->orderByDesc('products.published_at')->orderByDesc('products.id'),
tests/Browser/SmokeTest.php:39:        ->where('status', 'published')
database/factories/ThemeFactory.php:26:            'published_at' => null,
database/factories/ThemeFactory.php:30:    public function published(): static
database/factories/ThemeFactory.php:34:            'published_at' => now(),
database/factories/ProductFactory.php:35:            'published_at' => now(),
database/factories/ProductFactory.php:43:            'published_at' => null,
app/Services/CartService.php:252:        if ($variant->product->status !== ProductStatus::Active || $variant->product->published_at === null) {
database/factories/ThemeFileFactory.php:24:            'theme_id' => Theme::factory(),
database/factories/ThemeFileFactory.php:26:            'storage_key' => 'themes/'.Str::random(12).'/'.$path,
tests/Feature/Storefront/ThemeDataTest.php:12:use App\Services\ThemeSettingsService;
tests/Feature/Storefront/ThemeDataTest.php:27:    expect(Schema::hasColumns('themes', ['store_id', 'name', 'version', 'status', 'published_at']))->toBeTrue()
tests/Feature/Storefront/ThemeDataTest.php:30:        ->and(Schema::hasColumns('pages', ['store_id', 'title', 'handle', 'body_html', 'status', 'published_at']))->toBeTrue()
tests/Feature/Storefront/ThemeDataTest.php:33:        ->and(Theme::withoutGlobalScopes()->count())->toBe(2)
tests/Feature/Storefront/ThemeDataTest.php:45:    Theme::factory()->published()->create(['store_id' => $firstStore->getKey(), 'name' => 'First Theme']);
tests/Feature/Storefront/ThemeDataTest.php:46:    Theme::factory()->published()->create(['store_id' => $secondStore->getKey(), 'name' => 'Second Theme']);
tests/Feature/Storefront/ThemeDataTest.php:47:    Page::factory()->published()->create(['store_id' => $firstStore->getKey(), 'handle' => 'first-page']);
tests/Feature/Storefront/ThemeDataTest.php:48:    Page::factory()->published()->create(['store_id' => $secondStore->getKey(), 'handle' => 'second-page']);
tests/Feature/Storefront/ThemeDataTest.php:54:    expect(Theme::query()->pluck('name')->all())->toBe(['First Theme'])
tests/Feature/Storefront/ThemeDataTest.php:62:    $firstTheme = Theme::factory()->published()->create(['store_id' => $firstStore->getKey()]);
tests/Feature/Storefront/ThemeDataTest.php:63:    $secondTheme = Theme::factory()->published()->create(['store_id' => $secondStore->getKey()]);
tests/Feature/Storefront/ThemeDataTest.php:81:test('theme settings service loads cached published settings with defaults', function () {
tests/Feature/Storefront/ThemeDataTest.php:85:    $settings = app(ThemeSettingsService::class)->forStore($store);
tests/Feature/Storefront/ThemeDataTest.php:112:test('storefront pages render published database content only', function () {
tests/Feature/Storefront/ThemeDataTest.php:124:        'published_at' => null,
database/factories/ThemeSettingsFactory.php:21:            'theme_id' => Theme::factory(),
tests/Feature/Foundation/AuthorizationTest.php:125:            'view' => Theme::class,
tests/Feature/Foundation/AuthorizationTest.php:126:            'update' => Theme::class,
tests/Feature/Foundation/AuthorizationTest.php:127:            'publish' => Theme::class,
tests/Feature/Foundation/AuthorizationTest.php:128:            'delete' => Theme::class,
app/Services/ProductService.php:46:                'published_at' => null,
app/Services/ProductService.php:137:            'published_at' => $newStatus === ProductStatus::Active
app/Services/ProductService.php:138:                ? ($product->published_at ?? now())
app/Services/ProductService.php:139:                : $product->published_at,
app/Livewire/Admin/Themes/Editor.php:10:use App\Services\ThemeSettingsService;
app/Livewire/Admin/Themes/Editor.php:33:    public function mount(Theme $theme, ThemeSettingsService $themeSettings): void
app/Livewire/Admin/Themes/Editor.php:92:    public function save(ThemeSettingsService $settings): void
app/Livewire/Admin/Themes/Editor.php:110:    public function publish(ThemeSettingsService $settings): void
app/Livewire/Admin/Themes/Editor.php:112:        $this->authorize('publish', $this->theme);
app/Livewire/Admin/Themes/Editor.php:115:            Theme::withoutGlobalScopes()
app/Livewire/Admin/Themes/Editor.php:119:                    'published_at' => null,
app/Livewire/Admin/Themes/Editor.php…6075 chars truncated…houtGlobalScopes()->where('store_id', $store->getKey())->firstOrFail();
database/seeders/PageSeeder.php:26:                        'status' => 'published',
database/seeders/PageSeeder.php:27:                        'published_at' => now(),
app/Livewire/Admin/Products/Form.php:48:    public ?string $publishedAt = null;
app/Livewire/Admin/Products/Form.php:375:        $this->publishedAt = $product->published_at?->format('Y-m-d\TH:i');
app/Livewire/Storefront/Pages/Show.php:22:            ->whereNotNull('published_at')
tests/Feature/Api/AdminPageApiTest.php:39:    Page::factory()->published()->create([
tests/Feature/Api/AdminPageApiTest.php:44:    Page::factory()->published()->create([
tests/Feature/Api/AdminPageApiTest.php:52:        ->getJson("/api/admin/v1/stores/{$store->getKey()}/pages?status=published&query=API")
tests/Feature/Api/AdminPageApiTest.php:61:            'status' => 'published',
tests/Feature/Api/AdminPageApiTest.php:66:        ->assertJsonPath('data.status', 'published');
tests/Feature/Api/AdminPageApiTest.php:70:    expect($page->published_at)->not->toBeNull();
tests/Feature/Api/AdminPageApiTest.php:94:    $page = Page::factory()->published()->create([
tests/Feature/Api/AdminPageApiTest.php:121:test('admin page api validates normalized handles and published dates', function (): void {
tests/Feature/Api/AdminPageApiTest.php:132:            'status' => 'published',
tests/Feature/Api/AdminPageApiTest.php:140:            'status' => 'published',
tests/Feature/Api/AdminPageApiTest.php:141:            'published_at' => 'not-a-date',
tests/Feature/Api/AdminPageApiTest.php:144:        ->assertJsonValidationErrors(['published_at']);
app/Livewire/Storefront/Collections/Show.php:73:            ->whereNotNull('products.published_at')
app/Livewire/Storefront/Collections/Show.php:99:            ->whereNotNull('products.published_at')
app/Livewire/Storefront/Collections/Show.php:110:            ->whereNotNull('products.published_at')
database/migrations/2026_05_03_223841_create_pages_table.php:20:            $table->enum('status', ['draft', 'published', 'archived'])->default('draft');
database/migrations/2026_05_03_223841_create_pages_table.php:21:            $table->timestamp('published_at')->nullable();
app/Livewire/Storefront/Collections/Index.php:16:                ->whereNotNull('published_at')])
database/seeders/ThemeSettingsSeeder.php:7:use App\Services\ThemeSettingsService;
database/seeders/ThemeSettingsSeeder.php:17:        Theme::withoutGlobalScopes()
database/seeders/ThemeSettingsSeeder.php:24:                        'settings_json' => app(ThemeSettingsService::class)->defaultsForStore($theme->store),
tests/Feature/Catalog/ProductServiceTest.php:65:        ->and($product->published_at)->not->toBeNull();
tests/Feature/Catalog/ProductServiceTest.php:137:        ->and($product->published_at)->not->toBeNull();
app/Livewire/Storefront/Search/Index.php:74:            ->whereNotNull('published_at')
app/Livewire/Storefront/Search/Index.php:86:            ->whereNotNull('published_at')
database/migrations/2026_05_03_223824_create_themes_table.php:14:        Schema::create('themes', function (Blueprint $table) {
database/migrations/2026_05_03_223824_create_themes_table.php:19:            $table->enum('status', ['draft', 'published'])->default('draft');
database/migrations/2026_05_03_223824_create_themes_table.php:20:            $table->timestamp('published_at')->nullable();
database/migrations/2026_05_03_223824_create_themes_table.php:33:        Schema::dropIfExists('themes');
app/Livewire/Storefront/Home.php:8:use App\Services\ThemeSettingsService;
app/Livewire/Storefront/Home.php:29:            ->whereNotNull('published_at')
app/Livewire/Storefront/Home.php:50:        return app(ThemeSettingsService::class)->forStore($this->store());
database/seeders/ProductSeeder.php:46:                    ...Arr::only($data, ['title', 'status', 'description_html', 'vendor', 'product_type', 'tags', 'published_at']),
database/seeders/ProductSeeder.php:336:        mixed $publishedAt = null,
database/seeders/ProductSeeder.php:348:            'published_at' => $status === 'draft' ? null : ($publishedAt ?? now()),
database/migrations/2026_05_03_213856_create_products_table.php:24:            $table->timestamp('published_at')->nullable();
database/migrations/2026_05_03_213856_create_products_table.php:30:            $table->index(['store_id', 'published_at']);
app/Livewire/Storefront/Products/Show.php:112:            ->whereNotNull('published_at')
database/seeders/ThemeFileSeeder.php:17:        Theme::withoutGlobalScopes()->get()->each(function (Theme $theme): void {
database/seeders/ThemeFileSeeder.php:24:                $storageKey = "themes/{$theme->getKey()}/{$path}";
database/seeders/ThemeSeeder.php:17:            Theme::withoutGlobalScopes()->updateOrCreate(
database/seeders/ThemeSeeder.php:24:                    'status' => 'published',
database/seeders/ThemeSeeder.php:25:                    'published_at' => now(),