AI-ready development

Task Recipes

Task Recipes

Copy-paste recipes for common post-generation tasks. Each recipe shows what to change, where to change it, and how to verify.

These recipes are the canonical patterns coding agents should follow when extending a Thunderclap-generated module. They sit on top of Thunderclap Recipes and AI Task Patterns.

Recipe: Add a business action to a generated module

Goal: add an approve action to a generated PurchaseOrder module.

Text
1. Create app/Actions/PurchaseOrder/ApproveAction.php (single-invokable)
2. Add a policy method PurchaseOrderPolicy::approve(User, PurchaseOrder)
3. Add a route in modules/PurchaseOrder/routes/web.php with can:purchase-order.approve
4. Add controller method PurchaseOrderController::approve(...)
5. Add Blade button in modules/PurchaseOrder/resources/views/show.blade.php
6. Add Pest test for the action class
7. Add feature test for the route + policy combination

See Business actions for the full pattern.

Verify:

Bash
php artisan test --filter=ApproveAction
php artisan route:list | grep purchase-orders.approve

Recipe: Add a custom permission to a generated module

Goal: give the manager role view-only access to Item.

Text
1. database/seeders/DemoSeeder.php — add permission 'item.view'
2. modules/Item/config/item.php — set 'permission' => 'item.view'
3. (already wired) routes/web.php applies can:item.view from config
4. Assign 'item.view' to the manager role in DemoSeeder
5. php artisan db:seed --class=DemoSeeder

Verify:

Bash
php artisan tinker
>>> User::where('email','manager@laravolt.dev')->first()->can('item.view')
=> true

Recipe: Customize generated form fields

Goal: replace the default text field for price with a masked currency input.

Text
1. Open modules/Item/resources/views/_form.blade.php
2. Replace:
{!! form()->text('price')->label('Price')->required() !!}
with:
{!! form()->number('price')->label('Price')->mask('currency')->required() !!}
3. Update modules/Item/Requests/Store.php and Update.php:
'price' => ['required', 'numeric', 'min:0']

See Forms overview and Input masking.

Recipe: Add a search filter to a generated index

Goal: add a status dropdown filter to the PurchaseOrder index.

Text
1. Open modules/PurchaseOrder/PurchaseOrderTableView.php
2. Override filters() and add Dropdown filter
3. In data(), apply the filter conditionally
PHP
use Laravolt\Suitable\Filters\Dropdown;
public function filters(): array
{
return [
Dropdown::make('status')->options([
'draft' => 'Draft',
'submitted' => 'Submitted',
'approved' => 'Approved',
]),
];
}
public function data(): Builder
{
$query = PurchaseOrder::query()
->autoSort($this->sortPayload())
->autoSearch(trim($this->search));
if ($this->filterValue('status')) {
$query->where('status', $this->filterValue('status'));
}
return $query->latest();
}

Recipe: Add a sidebar menu entry without regenerating

Goal: add a menu entry for a hand-built module.

Text
1. Open the module's ServiceProvider
2. Override menu() and call app('laravolt.menu.builder')->register(...)
3. Pass ->data('permission', 'your.permission') to gate visibility
PHP
public function menu(): void
{
app('laravolt.menu.builder')->register(function ($menu) {
if ($menu->modules) {
$menu->modules
->add('My Module', route('modules:my-module.index'))
->data('icon', 'sparkles')
->data('permission', 'my-module.view')
->active('modules/my-module/*');
}
});
}

Recipe: Add a browser smoke test for a generated module

Goal: assert the generated index renders and an admin can navigate to create.

PHP
// tests/Browser/{Module}/{Module}CrudTest.php
use App\Models\User;
use Database\Seeders\DemoSeeder;
beforeEach(function () {
$this->seed(DemoSeeder::class);
$this->admin = User::query()->where('email', 'admin@laravolt.dev')->firstOrFail();
});
it('renders index for admin', function () {
$this->actingAs($this->admin);
visit('/modules/items')
->assertSee('Items')
->assertNoJavaScriptErrors();
})->group('browser');

Run with pest --group=browser.

Recipe: Re-baseline screenshot snapshots after UI change

Bash
php vendor/bin/pest --testsuite Browser --update-snapshots
git diff --stat tests/.pest/snapshots/
git add tests/.pest/snapshots/

Always review the diff visually before committing — the snapshot is a hash of the rendered PNG, not the image itself.

Previous
Thunderclap Recipes